이 문서는 React Sample 템플릿의 코드 배치와 책임 경계를 설명합니다.
- 라우팅, 서버 상태, 클라이언트 UI 상태, 폼 검증, mock API, 테스트 흐름을 한 프로젝트 안에서 확인한다.
- 기능 단위 코드와 공통 코드를 분리해 작은 실무형 React 앱의 기본 구조를 보여준다.
- 새 기능을 추가할 때 어느 폴더에 무엇을 넣을지 예측 가능하게 만든다.
src/app 앱 부트스트랩, provider, router, 전역 스타일
src/routes TanStack Router file route 정의
src/layouts Dashboard/Auth 레이아웃과 헤더 UI
src/features 도메인 기능 단위 코드
src/pages 독립 페이지 예제
src/shared 도메인 독립 UI 컴포넌트와 유틸
src/stores Zustand 상태 (UI / 인증 / 알림)
src/mocks MSW handler, browser/server setup, fixture
src/test Vitest setupmain.tsx는 React root를 만들고 환경변수(VITE_ENABLE_MOCKS)에 따라 MSW worker를 시작한다.App.tsx는 provider와 router를 연결한다.router.tsx는 TanStack Router 인스턴스를 생성하고context로 auth store를 주입한다.router-context.ts는 라우터 context 타입({ auth })을 정의해beforeLoad에서 인증 상태를 읽게 한다.providers/QueryProvider.tsx는 앱 전체 React Query client를 제공한다.providers/AppProviders.tsx는 theme/density를 document 속성에 동기화하고 전역Toaster를 마운트한다.
- TanStack Router의 file route만 둔다.
- route 파일은 URL과 page component를 연결하는 얇은 계층으로 유지한다.
- 실제 화면 구현은
features또는pages에 둔다. _dashboard.tsx는beforeLoad로 미인증 접근을 막고/signin으로 리다이렉트하는 보호 라우트다.signin.tsx는validateSearch로redirect검색 파라미터를 파싱한다.- 라우트 컴포넌트는 Vite 플러그인의
autoCodeSplitting으로 라우트별 청크로 분리된다.
DashboardLayout은 사이드바, 상단 헤더, theme toggle, 로그아웃 버튼, main outlet을 담당한다.ThemeToggle은 Zustandtheme상태를 사용하는 우측 상단 day/night 토글 예제다.AuthLayout은 dashboard shell이 필요 없는 로그인 화면의 중앙 정렬 레이아웃이다.
기능 단위 코드를 모으는 영역입니다.
features/projects/api fetch wrapper
features/projects/queries TanStack Query key/options/hooks
features/projects/model type, schema, pure utility
features/projects/hooks feature 전용 hook
features/projects/components feature 전용 UI
features/projects/pages route가 렌더링하는 page component
features/auth/api 로그인 요청 wrapper (signInRequest)dashboard, projects, settings, auth는 서로 다른 기능 경계를 보여주는 예제입니다.
- feature에 종속되지 않는 UI primitive와 유틸을 둔다.
shared/ui는 Button, Card, Dialog, Input, Select,Toaster같은 재사용 컴포넌트다.shared/api는 공통apiRequest와ApiError로 HTTP 오류와 성공 응답 검증을 담당한다.shared/lib는cn,formatDate, test helper처럼 도메인과 무관한 함수를 둔다.shared/config/env.ts는import.meta.env를 Zod로 검증한 타입 안전 환경변수를 제공한다.
- Zustand 기반 client 상태를 둔다.
ui-store는sidebarOpen,theme,density를 관리한다.theme/density는 localStorage keyreact-sample-ui에 저장하고,sidebarOpen은 일시적 shell 상태라 저장 대상에서 제외한다.auth-store는token,user,isAuthenticated를 관리하며 localStorage keyreact-sample-auth에 저장한다. 라우터 context로 주입되어beforeLoad가드가 이 상태를 읽는다.toast-store는 전역 알림 목록과toast.success/error/info헬퍼를 제공한다(영속화하지 않음).- 서버에서 가져오는 프로젝트 데이터는 이곳에 두지 않고 TanStack Query에 맡긴다.
- MSW handler와 fixture를 둔다.
- 개발 환경에서는 browser worker가
/api/projects,/api/login등 요청을 가로챈다. - 테스트 환경에서는 server setup이 같은 handler를 사용한다.
- 실패 handler는 공통 error helper로 body와
X-Trace-Id에 같은 trace ID를 제공한다.
route file
-> page component
-> query hook / mutation hook
-> API function
-> apiRequest("/api/...", { schema })
-> fetch("/api/...")
-> MSW handler
-> JSON
-> Zod response schema
-> typed feature dataHTTP 실패는 ApiError(status, code, message, path, traceId)로 정규화합니다.
성공 응답이 JSON 또는 feature schema를 위반하면 INVALID_RESPONSE, 네트워크
요청 자체가 실패하면 NETWORK_ERROR로 구분합니다. 원본 오류 body나 인증
정보는 오류 객체에 저장하지 않습니다.
shared는 feature, route, page, layout, app을 알지 않는다.- feature의
api,model,queries는 route, page, layout, app, store를 알지 않는다. - route는 URL과 page 연결을 담당하고 비즈니스 로직을 직접 구현하지 않는다.
- 현재 dashboard가 projects의 조회 모델과 UI를 조합하는 방향은 허용한다.
최소 의존 방향은 ESLint no-restricted-imports로 검증합니다. feature 간
전면 격리는 현재 템플릿 범위가 아니며, 필요한 경우 public API 또는 별도
composition 계층을 먼저 설계합니다.
클라이언트 UI 상태는 별도 흐름을 사용합니다.
layout/settings component
-> useUiStore selector
-> Zustand store
-> component re-render미인증 사용자가 보호 라우트에 접근하면 beforeLoad가 로그인으로 리다이렉트하고, 로그인 후 원래 목적지로 복귀합니다.
보호 라우트 접근 (예: /settings)
-> _dashboard beforeLoad
-> context.auth.getState().isAuthenticated == false
-> redirect("/signin?redirect=/settings")
-> SignInPage 제출
-> signInRequest() -> MSW POST /api/login -> token 발급
-> authStore.signIn({ token, user }) (localStorage 저장)
-> navigate({ to: search.redirect ?? "/" })
-> 이제 beforeLoad 통과 -> 원래 목적지 렌더로그아웃은 authStore.signOut()으로 상태를 비우고 /signin으로 이동합니다.
mutation onSuccess / onError 등 어디서든
-> toast.success("...")
-> toastStore.add(...)
-> AppProviders에 마운트된 Toaster가 렌더
-> 각 ToastItem이 4초 후 자동 dismiss- 서버 데이터: TanStack Query
- 클라이언트 UI 상태 / 인증 / 알림: Zustand (
ui-store,auth-store,toast-store) - 폼 상태: React Hook Form
- 입력 검증: Zod
- API 성공 응답 검증: Zod +
apiRequest - API mocking: MSW
- 새 URL이 필요하면
src/routes에 route 파일을 추가한다. - 특정 도메인 기능이면
src/features/<feature-name>아래에 page, component, query, model을 둔다. - 여러 기능에서 재사용되면
src/shared로 이동한다. - 서버 데이터 cache나 mutation은 TanStack Query를 우선 사용한다.
- 단순 UI preference나 shell 상태는 Zustand store에 둔다.
- 새 기능 완료 전 기능 추가 체크리스트를 확인한다.