job-is-fe는 React 19 + Vite 기반 CSR SPA다.
- 라우팅과 페이지 조립은
pages가 담당한다. - 도메인 전용 UI/훅/로직은
features/{domain}에 둔다. - 도메인에 종속되지 않는 공용 자원은
components,hooks,utils,constants에 둔다. - API 요청 함수는
api에 둔다. - 새 최상위 폴더를 추측해서 만들지 않고 아래 baseline을 따른다.
src/
├── api/ # API 클라이언트(client.ts, env.ts), 요청 함수
├── assets/
│ ├── icons/ # SVG 아이콘 (vite-plugin-svgr로 컴포넌트처럼 import)
│ └── images/ # 이미지 등 나머지 정적 자산
├── components/
│ ├── common/ # 도메인에 종속되지 않는 전역 재사용 UI 컴포넌트
│ ├── feedback/ # 로딩/에러/빈 상태 등 피드백 UI 컴포넌트
│ └── layout/ # 헤더, 네비게이션 등 레이아웃 셸 컴포넌트
├── constants/ # 공용 상수
├── features/
│ └── {domain}/ # 특정 도메인(예: auth, jobs) 전용 컴포넌트/훅/로직
│ ├── components/
│ └── hooks/
├── hooks/ # 도메인에 종속되지 않는 공용 커스텀 훅
├── pages/
│ └── {PageName}/ # 페이지별 폴더 (예: pages/HomePage/HomePage.tsx)
├── providers/ # 앱 전역 Provider (QueryProvider 등)
├── routes/ # createBrowserRouter 라우트 정의
├── styles/ # 전역 스타일, Tailwind 진입점(index.css, 컬러 토큰 @theme)
├── utils/ # 공용 유틸리티 함수 (cn.ts 등)
├── App.tsx
├── main.tsx
└── vite-env.d.ts
- Framework: React 19 + TypeScript
- Bundler: Vite (
resolve.tsconfigPaths로@/*alias,vite-plugin-svgr로 SVG 아이콘 컴포넌트 import) - Styling: Tailwind CSS v4 (
@tailwindcss/vite, 설정 파일 없음, 컬러 토큰은src/styles/index.css의@theme에 정의),class-variance-authority(cva) +clsx+tailwind-merge(src/utils/cn.ts)로 조건부 클래스 처리 - Routing:
react-router(v8)createBrowserRouter,src/routes/router.tsx - Server State: TanStack Query,
src/providers/QueryProvider.tsx에서QueryClientProvider관리 - Client State: Zustand (설치만 되어 있고 실제 스토어는 없음, 8절 State Policy 참고)
- HTTP Client: axios,
src/api/client.ts+src/api/env.ts(VITE_API_BASE_URL) - Lint/Format: ESLint(flat config) + Prettier
- Package Manager: pnpm
- Git Hooks: husky + lint-staged(커밋 전 자동 lint/format) + commitlint(커밋 메시지 검사)
| 위치 | 책임 |
|---|---|
pages/{PageName} |
컴포넌트/훅/feature 모듈을 조합해 라우트 화면을 구성한다. 비즈니스 로직을 직접 갖지 않는다 |
routes |
createBrowserRouter로 경로와 페이지를 연결한다. 실제로 쓰는 라우트만 등록한다 |
providers |
앱 전역 Provider를 관리한다. App.tsx가 조합한다 |
features/{domain} |
특정 도메인 전용 UI/훅/로직을 담당한다. 다른 도메인 폴더를 직접 import하지 않는다 |
components/common, components/feedback, components/layout |
도메인 데이터 모델을 모르는 순수 재사용 UI. props로만 데이터를 받는다 |
hooks |
도메인에 종속되지 않는 공용 로직 |
api |
API 클라이언트와 요청 함수. React 컴포넌트를 import하지 않는다. 컴포넌트는 endpoint 경로를 직접 알지 못하고 이 레이어를 통해서만 호출한다 |
utils, constants, assets, styles |
순수 유틸/상수/자산/전역 스타일 |
features/{domain}하위 구조는 실제로 필요한 시점에 만든다. 빈 도메인 폴더를 미리 채우지 않는다.- 도메인 전용 컴포넌트/훅이 다른 도메인에서도 필요해지면
components/common또는hooks로 승격한다.
- alias는
@/*(src/*)만 정의되어 있다. 가독성이 좋아지는 경우 alias를 쓰고, 같은/인접 폴더 파일은 상대 import를 우선한다. - 새 alias(
@components,@hooks등)를 임의로 추가하지 않는다. import/orderESLint 규칙이 그룹/알파벳 순 정렬을 강제한다.
- 컴포넌트는 화살표 함수가 아닌
function선언으로 작성한다. className, 필요하면ref를 prop으로 항상 노출한다.- variant가 2~3개를 넘으면
cva로, 조건부 클래스가 단순하면cn()+ 삼항/객체로 처리한다. - 상태 전이가 많은 컴포넌트는
data-*속성 + Tailwind 임의 변형자를 쓴다. - 상세 규칙은
docs/CONVENTIONS.md를 따른다.
| 상태 | 위치 |
|---|---|
| 서버에서 온 데이터 | TanStack Query (useQuery/useMutation) |
| 폼 입력 중인 값 | 해당 컴포넌트/훅의 local state |
| URL로 표현 가능한 값 | route params 또는 search params |
| 모달 열림/닫힘 | 해당 컴포넌트의 local state |
| 여러 컴포넌트가 공유하는 순수 클라이언트 상태 | Zustand (설치는 되어 있으나 아직 실제 스토어 없음) |
Zustand 스토어는 실제로 여러 컴포넌트가 공유해야 하는 클라이언트 상태(로그인 세션 등)가 생기는 시점에 만든다. 미리 빈 스토어를 만들어두지 않는다.
테스트 도구는 아직 설치되어 있지 않다. 도입하면 구현 파일이 있는 폴더의 __tests__/에 둔다.
src/utils/cn.ts
src/utils/__tests__/cn.test.ts