Frontend 프로젝트 가이드
Vite + React + TypeScript 기반 프론트엔드 프로젝트 개발 가이드입니다.
목차
- 개발 규칙
- 개발 환경 및 옵션 리스팅
- 설치 및 동작 방법
- 아키텍처 & 책임 분리 규칙
- 네이밍 & 패키지 규칙
- 예외 처리 & 에러 응답 규칙
- 로그 & 감사(Audit) 규칙
- 설정 관리 규칙
- 보안 관련 추가 규칙
- 테스트 & 검증 규칙
- 금지 패턴 / 안티 패턴 목록
- 성능 최적화 가이드
- 의존성 관리
- 트러블슈팅 가이드
1. 개발 규칙
1.1 시큐어 코딩 규칙 준수
- OWASP Top 10 기반 보안 규칙 준수 필수
- XSS 방지:
dangerouslySetInnerHTML금지, 필요 시 DOMPurify 사용 - 인증 정보 저장 금지:
localStorage/sessionStorage에 토큰 저장 금지 - URL 인코딩:
encodeURIComponent사용 필수 eval/new Function사용 금지- 환경 변수에 비밀키 저장 금지
상세 규칙은 base_arcitectures_md/SECURE_RULE.md 참조
1.2 공통 소스 기능 및 활용 방법
공통 API (common/api/)
-
http.ts: Axios 인스턴스baseURL,timeout,withCredentials설정- 전역 헤더 설정
-
endpoints.ts: API 엔드포인트 상수import { API } from "@/common/api/endpoints"; const response = await http.get(API.AUTH.ME); -
interceptors.ts: 전역 인터셉터- 요청: 전역 로딩 시작
- 응답: 전역 로딩 종료, 401/403 처리
공통 인증 (common/auth/)
-
AuthContext: 인증 상태 Context -
AuthProvider: 인증 상태 제공자 -
useAuth: 인증 훅const { user, login, logout, refreshMe } = useAuth(); -
ProtectedRoute: 인증 필요 라우트 보호<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} /> -
authService: 인증 API 호출login(),logout(),me()
공통 컴포넌트 (common/components/)
디렉터리 구조:
common/components/
├── form/ # 폼 입력 컴포넌트 (Input, Select, Textarea, Checkbox, Radio)
├── action/ # 액션 컴포넌트 (Button)
├── display/ # 표시 컴포넌트 (Table)
├── system/ # 시스템 컴포넌트 (LoadingOverlay, LoadingProvider, Toast, CommonModal)
└── index.ts # 통합 export
공통 원칙:
- 모든 공통 컴포넌트는
id,name,className전달 옵션 지원 - 스타일 관련 세부 구현은 CSS로 처리 가능한 영역은 최대한 CSS에 위임
- 컴포넌트는 범용성 위주로 설계하되, 과도한 추상화는 지양
- 동작이 필요한 부분은 props 옵션으로만 제어
입력 / 액션 계열 컴포넌트:
-
Button.tsx: 버튼 컴포넌트import { Button } from "@/common/components"; <Button disabled={false} loading={false} size="md" // "sm" | "md" | "lg" variant="primary" // "primary" | "secondary" | "danger" | "outline" | "ghost" onClick={() => {}} > 버튼 텍스트 </Button>disabled: 비활성화loading: 로딩 상태 (중복 클릭 방지)size: 버튼 크기 (sm,md,lg)variant: 버튼 스타일 변형
-
Input.tsx: 입력 컴포넌트 (검색 기능 포함)import { Input } from "@/common/components"; // 기본 텍스트 입력 <Input type="text" value={value} onChange={setValue} format={{ formatEmail: true }} // 이메일 포맷 format={{ formatPhone: true }} // 전화번호 포맷 (010-1234-5678) format={{ validationRegex: /^[a-z]+$/ }} // 정규식 검증 search={{ enabled: true, debounceMs: 300, commitOnEnter: true, clearOnEscape: true, onCommit: (value) => {} }} /> // 비밀번호 입력 <Input type="password" value={value} onChange={setValue} /> // 숫자 입력 (숫자 이외 문자 자동 제거) <Input type="number" value={value} onChange={setValue} />- 지원 타입:
text,password,number format: 이메일/전화번호 자동 포맷, 정규식 검증search: debounce, commitOnEnter, clearOnEscape 옵션
- 지원 타입:
-
Select.tsx: 선택 컴포넌트import { Select } from "@/common/components"; <Select options={[ { value: "1", label: "옵션 1" }, { value: "2", label: "옵션 2", disabled: true } ]} value={selectedValue} onChange={(value) => {}} placeholder="선택하세요" /> -
Textarea.tsx: 텍스트 영역 컴포넌트import { Textarea } from "@/common/components"; <Textarea value={value} onChange={setValue} rows={5} />- 기본 속성:
resize: none,width: 100%
- 기본 속성:
-
Checkbox.tsx: 체크박스 컴포넌트import { Checkbox } from "@/common/components"; <Checkbox checked={checked} onChange={setChecked} label="체크박스 레이블" /> -
Radio.tsx: 라디오 버튼 컴포넌트import { Radio } from "@/common/components"; <Radio name="radio-group" checked={selected === "value"} onChange={(checked) => {}} label="라디오 레이블" />
표시 계열 컴포넌트:
-
Table.tsx: 표준 테이블 컴포넌트import { Table } from "@/common/components"; <Table columns={columns} rows={rows} getRowId={(row) => row.id} loading={isLoading} emptyText="데이터가 없습니다" onRowClick={(row) => {}} selection={selectionState} sort={sortValue} onSortChange={setSortValue} />- 로딩, 빈 상태, 선택, 정렬, 페이징 지원
-
CommonModal.tsx: 공통 모달 컴포넌트import { CommonModal } from "@/common/components"; <CommonModal open={isOpen} onClose={() => setIsOpen(false)} title="모달 제목" showFooter={true} onConfirm={() => {}} // 전달 시 "확인", "닫기" 버튼 모두 표시 confirmText="확인" cancelText="닫기" width="50vw" // 기본값 height="50vh" // 기본값 > 모달 컨텐츠 </CommonModal>- 구조: 헤더(타이틀, X 닫기) / 컨텐츠 / 푸터(확인/닫기 버튼)
onConfirm전달 시: "확인", "닫기" 버튼 모두 표시onConfirm없을 시: "확인" 버튼만 표시 (단순 닫기)- ESC 키로 닫기 지원
- 기본 사이즈:
width: 50vw,height: 50vh
시스템 컴포넌트:
-
LoadingOverlay.tsx: 전역 로딩 오버레이import { LoadingOverlay } from "@/common/components"; <LoadingOverlay visible={isLoading} /> -
LoadingProvider.tsx: 전역 로딩 상태 관리import { LoadingProvider, useLoading } from "@/common/components"; const { visible } = useLoading(); // API 호출 시 자동으로 로딩 표시 -
Toast.tsx: 토스트 알림 시스템import { useToast } from "@/common/components"; const toast = useToast(); toast.push("작업이 완료되었습니다.", "success"); // "success" | "error" | "warning" | "info"
통합 Import:
// 통합 import (권장) - 모든 컴포넌트를 한 번에 import
import {
Button,
Input,
Select,
Textarea,
Checkbox,
Radio,
CommonModal,
Table,
LoadingOverlay,
LoadingProvider,
useLoading,
useToast
} from "@/common/components";
// 또는 디렉터리별 import
import { Button } from "@/common/components/action";
import { Input, Select, Textarea, Checkbox, Radio } from "@/common/components/form";
import { Table } from "@/common/components/display/table";
import { CommonModal, LoadingOverlay, LoadingProvider, useToast } from "@/common/components/system";
공통 훅 (common/hooks/)
-
useFilterQuery: URL 검색 파라미터 관리- 뒤로가기/앞으로가기/새로고침 시 상태 유지
const { state, update } = useFilterQuery(); // state: { q, page, size, sort, filters } // update({ q: "검색어" }): URL 업데이트 -
useAppError: 에러 처리 훅const { handle } = useAppError(); try { await someApiCall(); } catch (error) { handle(error); // 자동으로 Toast 표시 }
공통 Query (common/query/)
queryClient.ts: TanStack Query 클라이언트 설정keys.ts: Query Key 표준화QueryProvider: TanStack Query 제공자 (옵션)
1.3 공통소스 활용해서 서비스 구현하는 규칙
-
Pages (
features/<domain>/pages/): 화면 구성만 담당useFilterQuery로 URL 상태 관리useAuth로 인증 상태 확인Table,Input등 공통 컴포넌트 사용
-
Components (
features/<domain>/components/): 도메인 특화 컴포넌트- 공통 컴포넌트를 조합하여 도메인 특화 UI 구성
-
API 호출:
http인스턴스 사용endpoints.ts의 상수 활용- 인터셉터가 자동으로 로딩/에러 처리
-
상태 관리:
- 전역 상태: Context API (
AuthContext) - 서버 상태: TanStack Query (옵션)
- URL 상태:
useFilterQuery
- 전역 상태: Context API (
1.4 공통 소스 수정 규칙
- 공통 소스는 불가피한 경우가 아니면 절대 수정 금지
- 추가(Extension)는 허용: 새로운 훅, 유틸 함수 추가 가능
- 수정이 필요한 경우 반드시 팀 리뷰 및 승인 필요
- 공통 소스 수정 시 모든 도메인에 미치는 영향 검토 필수
2. 개발 환경 및 옵션 리스팅
2.1 필수 환경
- Node.js: 18.x 이상
- npm: 9.x 이상 (또는 yarn, pnpm)
- 브라우저: Chrome, Firefox, Safari, Edge 최신 버전
2.2 주요 의존성
react: ^19.2.0react-dom: ^19.2.0react-router-dom: ^6.25.1axios: ^1.7.9typescript: ~5.9.3vite: ^7.2.4
2.3 옵션 기능
- TanStack Query: 서버 상태 관리 (옵션)
- 활성화:
.env에VITE_USE_REACT_QUERY=true - 비활성화:
VITE_USE_REACT_QUERY=false또는 미설정
- 활성화:
2.4 개발 도구
- ESLint: 코드 품질 검사
- TypeScript: 타입 안정성
- Vite: 빠른 개발 서버 및 빌드
3. 설치 및 동작 방법
3.1 사전 준비
-
Node.js 설치 확인
node -v npm -v -
환경 변수 파일 생성
.env.development: 개발 환경.env.production: 운영 환경- 자세한 내용은
ENV_SETUP.md참조
3.2 프로젝트 설정
-
의존성 설치
cd frontend npm install또는
npm ci -
환경 변수 설정
.env.development파일 생성 및 설정
VITE_API_BASE_URL=http://localhost:8080 VITE_API_PREFIX=/api VITE_USE_REACT_QUERY=false
3.3 실행
-
개발 서버 실행
npm run dev- 기본 주소:
http://localhost:5173
- 기본 주소:
-
빌드
npm run build- 빌드 결과:
dist/디렉터리
- 빌드 결과:
-
프리뷰 (빌드 결과 확인)
npm run preview
3.4 확인
- 개발 서버 실행 후 브라우저에서
http://localhost:5173접속 - 콘솔에서 에러 없이 로드되는지 확인
4. 아키텍처 & 책임 분리 규칙
4.1 계층별 책임 명확화
Pages (features/<domain>/pages/)
- 책임: 화면 구성 및 조합
- 역할:
- 라우팅 대상 컴포넌트
- 공통 컴포넌트 조합
- URL 상태 관리 (
useFilterQuery) - 인증 상태 확인 (
useAuth)
- 금지:
- 직접 API 호출 (Service 계층 사용 권장)
- 복잡한 비즈니스 로직 포함 ❌
Components (features/<domain>/components/)
- 책임: 도메인 특화 UI 컴포넌트
- 역할:
- 공통 컴포넌트 조합
- 도메인 특화 로직 (프레젠테이션 로직만)
- 금지:
- 직접 API 호출 ❌
- 전역 상태 직접 조작 ❌
Common (common/)
- 책임: 공통 기능 제공
- 구성:
api/: API 통신 (http, endpoints, interceptors)auth/: 인증 관리 (Context, Provider, Service)components/: 공통 UI 컴포넌트hooks/: 공통 훅query/: TanStack Query 설정 (옵션)utils/: 유틸리티 함수types/: 공통 타입
4.2 비즈니스 로직 위치 규칙
| 위치 | 비즈니스 로직 허용 여부 |
|---|---|
| Pages | ❌ 금지 (프레젠테이션 로직만) |
| Components | ❌ 금지 (프레젠테이션 로직만) |
| Common/Utils | ❌ 금지 (순수 함수만) |
| Service (선택) | ⭕ 허용 (API 호출 및 데이터 변환) |
참고: 프론트엔드는 주로 프레젠테이션 로직만 포함하며, 복잡한 비즈니스 로직은 백엔드에서 처리.
예시:
// ❌ 잘못된 예: Page에 복잡한 로직
function UserListPage() {
const [users, setUsers] = useState([]);
useEffect(() => {
// 복잡한 필터링/정렬 로직
const filtered = users.filter(...).sort(...);
setUsers(filtered);
}, []);
}
// ⭕ 올바른 예: 백엔드에서 필터링/정렬 처리
function UserListPage() {
const { state, update } = useFilterQuery();
const { data } = useQuery({
queryKey: ["users", state],
queryFn: () => fetchUsers(state), // 백엔드에 필터링/정렬 파라미터 전달
});
}
4.3 공통 모듈과 도메인 모듈의 경계 규칙
-
공통 모듈 (
common/): 모든 도메인에서 공통으로 사용하는 기능- 도메인 특화 로직 포함 금지
- 재사용 가능한 순수 함수/컴포넌트만
-
도메인 모듈 (
features/<domain>/): 특정 도메인 전용 기능- 다른 도메인에서 직접 참조 금지
- 도메인 간 공통 기능은
common/으로 이동
4.4 "이 로직은 어디에 두어야 하는가" 판단 기준
- URL 상태 관리:
useFilterQuery(Pages에서 사용) - 인증 상태:
useAuth(Pages/Components에서 사용) - API 호출:
http인스턴스 또는 TanStack Query - 전역 로딩: 자동 처리 (interceptors)
- 에러 처리:
useAppError또는 TanStack Query 에러 핸들링 - 공통 UI:
common/components/의 컴포넌트 사용
5. 네이밍 & 패키지 규칙
5.1 디렉터리 구조 규칙
app/: 앱 레벨 설정 (라우팅, 레이아웃)common/: 공통 기능features/: 도메인별 기능features/<domain>/pages/: 페이지 컴포넌트features/<domain>/components/: 도메인 특화 컴포넌트
5.2 파일 / 컴포넌트 / 함수 네이밍 기준
파일
- 컴포넌트:
PascalCase.tsx(예:UserList.tsx,LoginForm.tsx) - 훅:
camelCase.ts+use접두사 (예:useAuth.ts,useFilterQuery.ts) - 유틸:
camelCase.ts(예:errorMapper.ts) - 타입:
camelCase.ts(예:api.ts,error.ts)
컴포넌트
- PascalCase 사용
- 명확한 의미 전달 (예:
UserList,DocumentTable,LoginForm)
함수/변수
- camelCase 사용
- boolean:
is,has,can접두사 (예:isLoading,hasPermission) - 이벤트 핸들러:
handle접두사 (예:handleSubmit,handleClick)
상수
- UPPER_SNAKE_CASE 사용 (예:
API_BASE_URL,MAX_FILE_SIZE)
5.3 약어 사용 허용 / 금지 리스트
허용 약어
api,auth,config,util,props,ctx,ref
금지 약어
usr(→user),svc(→service),cmp(→component)mgr(→manager),info(→information),num(→number)
5.4 API 필드 ↔ 컴포넌트 Props ↔ 상태 변수 매핑 규칙
API 응답 → 컴포넌트
- 카멜 케이스 유지 (JSON 기본)
- API:
{"userId": 1, "email": "user@example.com"} - TypeScript:
{ userId: number; email: string }
- API:
컴포넌트 Props
- 카멜 케이스 사용
type UserCardProps = { userId: number; email: string; displayName: string; };
상태 변수
- 카멜 케이스 사용
const [user, setUser] = useState<User | null>(null); const [isLoading, setIsLoading] = useState(false);
6. 예외 처리 & 에러 응답 규칙
6.1 공통 Exception 구조
AppError 타입
type AppError = {
kind: "network" | "timeout" | "unauthorized" | "forbidden" | "notfound" | "validation" | "server" | "unknown";
status?: number;
code?: string;
message: string;
details?: unknown;
};
errorMapper
mapAxiosError():AxiosError→AppError변환- 자동으로 에러 종류 분류 및 사용자 친화적 메시지 제공
6.2 비즈니스 예외 vs 시스템 예외 구분
| 예외 타입 | 발생 위치 | 처리 방법 | 사용자 메시지 |
|---|---|---|---|
| 비즈니스 예외 | API 응답 (4xx) | useAppError.handle() |
서버 메시지 또는 기본 메시지 |
| 시스템 예외 | 네트워크/타임아웃 | useAppError.handle() |
일반화된 메시지 |
예시:
// useAppError로 자동 처리
const { handle } = useAppError();
try {
await someApiCall();
} catch (error) {
handle(error); // 자동으로 Toast 표시 및 AppError 반환
}
6.3 API 응답 포맷 통일 규칙
모든 API 응답은 다음 형식:
type ApiResponse<T> = {
success: boolean;
data: T | null;
error: {
code: string;
message: string;
} | null;
};
사용 예시:
const response = await http.get<ApiResponse<User>>(API.AUTH.ME);
if (response.data.success) {
const user = response.data.data; // User 타입
} else {
const error = response.data.error; // { code, message }
}
6.4 로그 기록 기준과 사용자 노출 메시지 분리 규칙
- 콘솔 로그: 개발 환경에서만 (
import.meta.env.DEV) - 사용자 메시지: Toast 또는 에러 페이지에 표시
- 민감 정보: 절대 로그/메시지에 포함하지 않음
예시:
// ❌ 잘못된 예: 민감 정보 콘솔 출력
console.log("User login:", { email, password });
// ⭕ 올바른 예: 개발 환경에서만 제한적 로깅
if (import.meta.env.DEV && import.meta.env.VITE_HTTP_LOGGING === "true") {
console.log("User login:", { email: maskEmail(email) });
}
7. 로그 & 감사(Audit) 규칙
7.1 로그 레벨 사용 기준
프론트엔드는 주로 console.log, console.warn, console.error 사용:
| 레벨 | 사용 시기 | 예시 |
|---|---|---|
| console.log | 개발 중 디버깅 | 파라미터 값, 상태 변화 |
| console.warn | 예상 가능한 문제 | API 응답 경고, 사용자 입력 경고 |
| console.error | 예상치 못한 오류 | 예외 발생, 시스템 오류 |
주의: 운영 빌드에서는 console.log 제거 (Vite가 자동 처리)
7.2 개인정보 및 민감정보 마스킹 규칙
마스킹 대상:
- 비밀번호, 토큰
- 이메일 (일부 마스킹 가능)
- 전화번호 (일부 마스킹 가능)
마스킹 방법:
- 직접 구현 또는 라이브러리 사용
- 로그/콘솔 출력 전 마스킹 필수
예시:
// ❌ 잘못된 예: 민감 정보 콘솔 출력
console.log("User data:", user);
// ⭕ 올바른 예: 마스킹 후 출력
console.log("User data:", {
...user,
email: maskEmail(user.email),
});
7.3 공통 로깅 유틸 사용 규칙
- Axios Interceptor: 자동 요청/응답 로깅 (개발 환경)
- 수동 로깅: 필요한 경우에만
console.log사용 - 운영 빌드: Vite가 자동으로
console.log제거
7.4 요청 추적용 식별자(Request ID 등) 사용 여부
현재는 기본 구조만 제공. 필요 시 다음 추가 가능:
- Axios Interceptor에서 Request ID 생성/추가
- 응답 헤더에서 Request ID 추출
- 로그에 Request ID 포함
8. 설정 관리 규칙
8.1 환경 변수 파일 분리 전략
.env.development: 개발 환경.env.production: 운영 환경.env.local: 로컬 오버라이드 (Git 제외)
환경 변수 접두사: VITE_ (Vite 요구사항)
8.2 환경별 설정 원칙
- 공통 설정: 코드에 하드코딩 (예: 기본 타임아웃)
- 환경별 설정: 환경 변수 사용
- 민감 정보: 환경 변수 사용 (절대 코드에 포함 금지)
8.3 옵션 처리 기준 (enable / disable 방식)
옵션 기능은 환경 변수로 제어:
# TanStack Query 사용 여부
VITE_USE_REACT_QUERY=true # 또는 false
코드에서 사용:
const useReactQuery = import.meta.env.VITE_USE_REACT_QUERY === "true";
8.4 TanStack Query 사용 여부를 옵션으로 제어하는 규칙
-
활성화:
VITE_USE_REACT_QUERY=trueQueryProvider가QueryClientProvider로 래핑- TanStack Query 훅 사용 가능
-
비활성화:
VITE_USE_REACT_QUERY=false또는 미설정QueryProvider가 children만 반환- 직접
http인스턴스로 API 호출
예시:
// TanStack Query 사용 (옵션 활성화 시)
const { data } = useQuery({
queryKey: ["users"],
queryFn: () => fetchUsers(),
});
// 직접 API 호출 (옵션 비활성화 시)
const [users, setUsers] = useState([]);
useEffect(() => {
http.get(API.USER.LIST).then(res => setUsers(res.data.data));
}, []);
9. 보안 관련 추가 규칙 (시큐어 코딩 보강)
9.1 인증 / 인가 흐름 준수 규칙
인증 (Authentication)
- 세션 기반 인증 사용
AuthProvider에서/api/auth/me호출로 인증 상태 확인- 로그인 성공 시 세션 쿠키 자동 설정 (백엔드)
인가 (Authorization)
- ProtectedRoute: 인증 필요 라우트 보호
- 백엔드에서 권한 검증 (403 Forbidden)
예시:
// ProtectedRoute 사용
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
9.2 세션 / 토큰 사용 시 주의 사항
- 세션 쿠키:
withCredentials: true설정 (Axios) - 토큰 저장 금지:
localStorage/sessionStorage에 토큰 저장 절대 금지 - 자동 갱신: 백엔드에서 세션 관리, 프론트엔드는
/api/auth/me로 상태 확인
9.3 파일 업로드 / 다운로드 처리 규칙
업로드
- 파일 크기 제한: 클라이언트에서 사전 검증
- 파일 타입 검증: 확장자 및 MIME 타입 확인
- FormData 사용:
multipart/form-data전송
다운로드
- 안전한 다운로드: 백엔드에서 권한 검증 후 다운로드 URL 제공
- Content-Disposition: 안전한 파일명 설정
9.4 외부 API 연동 시 보안 체크리스트
- CORS 설정 확인 (백엔드)
- HTTPS만 사용 (HTTP 금지)
- 타임아웃 설정 (무한 대기 방지)
- 입력값 검증 (외부 API로 전송 전)
- 응답 검증 (예상 형식 확인)
9.5 XSS 방지 규칙
dangerouslySetInnerHTML금지: HTML 직접 삽입 금지- 필요 시 DOMPurify: HTML 삽입이 불가피한 경우 DOMPurify로 sanitize
- URL 인코딩:
encodeURIComponent사용 eval/new Function금지: 동적 코드 실행 금지
예시:
// ❌ 잘못된 예: XSS 위험
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ⭕ 올바른 예: DOMPurify 사용
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
// ⭕ 더 안전한 예: 텍스트로 렌더링
<div>{userInput}</div>
10. 테스트 & 검증 규칙
10.1 단위 테스트 작성 기준 (필수 / 선택 구분)
선택 테스트
- 컴포넌트 테스트: React Testing Library 사용 (선택)
- 훅 테스트: 커스텀 훅 테스트 (선택)
- 유틸 테스트: 순수 함수 테스트 (선택)
예시:
import { render, screen } from "@testing-library/react";
import { UserList } from "./UserList";
test("renders user list", () => {
render(<UserList users={mockUsers} />);
expect(screen.getByText("User 1")).toBeInTheDocument();
});
10.2 테스트용 데이터 작성 규칙
- Mock 데이터:
__mocks__/디렉터리에 정의 - 격리: 각 테스트는 독립적으로 실행 가능해야 함
10.3 로컬 테스트 → 통합 테스트 흐름
- 로컬 컴포넌트 테스트: 단위 테스트
- 로컬 통합 테스트: 여러 컴포넌트 조합 테스트
- E2E 테스트: 실제 브라우저 테스트 (선택, Playwright/Cypress)
10.4 테스트 미수행 시 병합 제한 여부
- 현재는 권고 사항 (필수 아님)
- 향후 CI/CD 파이프라인에서 테스트 실패 시 병합 차단 가능
11. 금지 패턴 / 안티 패턴 목록
11.1 공통 Util에 비즈니스 로직 포함 ❌
// ❌ 잘못된 예
export function isAdminUser(email: string): boolean {
return email.includes("@admin.com");
}
// ⭕ 올바른 예: 도메인 로직은 해당 도메인에 위치
// (또는 백엔드에서 처리)
11.2 직접 API 호출 (인터셉터 우회) ❌
// ❌ 잘못된 예: axios 직접 import
import axios from "axios";
const response = await axios.get("http://localhost:8080/api/users");
// ⭕ 올바른 예: 공통 http 인스턴스 사용
import { http } from "@/common/api/http";
import { API } from "@/common/api/endpoints";
const response = await http.get(API.USER.LIST);
11.3 옵션 무시 후 하드코딩 ❌
// ❌ 잘못된 예: 옵션 무시하고 하드코딩
import { useQuery } from "@tanstack/react-query";
const { data } = useQuery({ ... });
// ⭕ 올바른 예: 옵션 확인
const useReactQuery = import.meta.env.VITE_USE_REACT_QUERY === "true";
if (useReactQuery) {
const { data } = useQuery({ ... });
} else {
const [data, setData] = useState(null);
useEffect(() => {
http.get(API.USER.LIST).then(res => setData(res.data.data));
}, []);
}
11.4 공통 코드 복사 후 개별 컴포넌트에 포함 ❌
// ❌ 잘못된 예: 공통 로직을 각 컴포넌트에 복사
function UserList() {
const [loading, setLoading] = useState(false);
// 로딩 처리 로직 복사
}
function DocumentList() {
const [loading, setLoading] = useState(false);
// 동일한 로딩 처리 로직 복사
}
// ⭕ 올바른 예: 공통 훅/컴포넌트 사용
function UserList() {
// 인터셉터가 자동으로 로딩 처리
const { data } = useQuery({ ... });
}
11.5 localStorage/sessionStorage에 인증 정보 저장 ❌
// ❌ 잘못된 예: 토큰을 localStorage에 저장
localStorage.setItem("token", token);
// ⭕ 올바른 예: 세션 쿠키 사용 (백엔드에서 관리)
// 프론트엔드는 withCredentials만 설정
11.6 dangerouslySetInnerHTML 사용 ❌
// ❌ 잘못된 예: XSS 위험
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ⭕ 올바른 예: 텍스트로 렌더링 또는 DOMPurify
<div>{userInput}</div>
// 또는
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
11.7 eval / new Function 사용 ❌
// ❌ 잘못된 예: 동적 코드 실행
eval(userInput);
new Function(userInput)();
// ⭕ 올바른 예: 안전한 방법 사용
// (동적 코드 실행이 필요한 경우는 거의 없음)
11.8 URL 상태 관리 무시 (useFilterQuery 미사용) ❌
// ❌ 잘못된 예: useState로만 상태 관리
const [q, setQ] = useState("");
const [page, setPage] = useState(1);
// ⭕ 올바른 예: useFilterQuery 사용 (뒤로가기/새로고침 대응)
const { state, update } = useFilterQuery();
// state: { q, page, size, sort, filters }
// update({ q: "검색어" }): URL 업데이트
11.9 전역 로딩 중복 처리 ❌
// ❌ 잘못된 예: 수동 로딩 처리
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
try {
await http.get(API.USER.LIST);
} finally {
setLoading(false);
}
};
// ⭕ 올바른 예: 인터셉터가 자동 처리
const fetchData = async () => {
await http.get(API.USER.LIST); // 인터셉터가 자동으로 로딩 처리
};
12. 성능 최적화 가이드
12.1 번들 크기 최적화
코드 스플리팅
- 라우트 기반 스플리팅: React.lazy 사용
const UserListPage = lazy(() => import("@/features/user/pages/UserListPage"));
불필요한 의존성 제거
- 트리 쉐이킹: 사용하지 않는 import 제거
- 대안 라이브러리: 가벼운 라이브러리 선택 (예:
date-fnsvsmoment)
12.2 렌더링 최적화
React 최적화
- useMemo / useCallback: 불필요한 재계산 방지
- React.memo: 컴포넌트 메모이제이션
- 가상화: 대량 리스트는
react-window사용
예시:
// ❌ 잘못된 예: 매번 새로운 객체 생성
function UserCard({ user }) {
const style = { color: "blue" }; // 매 렌더링마다 새 객체
return <div style={style}>{user.name}</div>;
}
// ⭕ 올바른 예: useMemo 사용
function UserCard({ user }) {
const style = useMemo(() => ({ color: "blue" }), []);
return <div style={style}>{user.name}</div>;
}
12.3 API 호출 최적화
중복 호출 방지
- TanStack Query: 자동 캐싱 및 중복 제거
- Debounce: 검색 입력 시 debounce 적용
데이터 페이징
- 서버 사이드 페이징: 대량 데이터는 페이징 필수
- 무한 스크롤: 필요 시
react-infinite-scroll활용
12.4 이미지 최적화
- 이미지 포맷: WebP 사용 권장
- 이미지 크기: 적절한 해상도로 리사이즈
- Lazy Loading:
loading="lazy"속성 사용
13. 의존성 관리
13.1 버전 관리 원칙
- 명시적 버전 지정:
package.json에 버전 명시 - 보안 패치 우선: 취약점 발견 시 즉시 업데이트
- 마이너 버전 업데이트: 정기적으로 검토 및 업데이트
13.2 보안 취약점 점검
정기 점검:
npm audit
npm audit fix
수동 점검:
- npm audit 활용
- GitHub Dependabot 설정 권장
13.3 업데이트 프로세스
- 의존성 업데이트:
package.json수정 - 로컬 테스트: 업데이트 후 빌드 및 테스트
- 통합 테스트: 개발 환경에서 검증
- 운영 배포: 검증 완료 후 배포
13.4 lock 파일 관리
- package-lock.json: Git에 커밋 필수
- npm ci 사용: CI/CD에서는
npm ci사용 (lock 파일 기반 설치)
14. 트러블슈팅 가이드
14.1 자주 발생하는 문제
문제 1: CORS 오류
증상: Access to XMLHttpRequest has been blocked by CORS policy
해결:
- 백엔드 CORS 설정 확인 (
WebMvcConfig.addCorsMappings) withCredentials: true설정 확인 (프론트엔드)- 백엔드에서
allowCredentials: true확인 - 프록시 설정 확인 (개발 환경)
문제 2: 세션이 유지되지 않음
증상: 로그인 후 요청 시 401 Unauthorized
해결:
withCredentials: true설정 확인 (Axios)- CORS 설정에서
allowCredentials: true확인 (백엔드) - 쿠키 도메인/경로 설정 확인
- 브라우저 쿠키 차단 여부 확인
문제 3: 환경 변수가 적용되지 않음
증상: import.meta.env.VITE_*가 undefined
해결:
- 환경 변수 파일 이름 확인 (
.env.development,.env.production) VITE_접두사 확인- 개발 서버 재시작
- 빌드 시 환경 변수 파일 위치 확인
문제 4: 빌드 실패 (TypeScript 오류)
증상: npm run build 실패
해결:
npm run lint로 오류 확인- TypeScript 타입 오류 수정
tsconfig.json설정 확인
14.2 디버깅 팁
React DevTools
- 컴포넌트 트리: 컴포넌트 상태 확인
- Profiler: 성능 분석
브라우저 DevTools
- Network 탭: API 호출 확인
- Console 탭: 에러 메시지 확인
- Application 탭: 쿠키/로컬 스토리지 확인
Vite DevTools
- HMR (Hot Module Replacement): 빠른 개발 피드백
- 에러 오버레이: 빌드 에러 즉시 확인
참고 문서
- 공통 보안 규칙:
SECURE_RULE.md - 환경 변수 설정:
ENV_SETUP.md