아키텍처
프론트엔드는 분리된 아키텍처를 사용합니다. Next.js App Router가 app/에서
라우팅, 레이아웃, 서버 엔트리포인트를 담당하고, 제품 코드는 src/에 두어
Feature-Sliced Design으로 구성합니다.
레이어링
리포지토리 수준 규칙은 다음과 같습니다.
shared -> entities -> features -> widgets -> app/
이 규칙은 파일시스템에서 다음과 같이 드러납니다.
| 레이어 | 여기에 들어가는 것 |
|---|---|
src/shared | API 기본 요소, env 설정, 범용 UI, provider, 횡단 관심 유틸리티 |
src/entities | 도메인 타입, 스키마, query 헬퍼, 생성된 클라이언트, 그리고 실제로 필요할 때의 엔티티 UI |
src/features | 로그인, 온보딩, 학생 관리, 리포트 생성 같은 사용자 대상 액션 |
src/widgets | 랜딩 헤더, 학생 사이드바, 인증 모달 같은 상위 수준 조합 |
app | 라우트 트리, 레이아웃, 로딩 상태, 에러 바운더리, 라우트 핸들러, 리다이렉트 |
루트 수준 예외는 의도된 것입니다.
components/ui/에는 포크된 shadcn 기본 요소가 들어 갑니다hooks/use-mobile.ts와lib/utils.ts는components.json이 그 위치를 기대하므로 리포지토리 루트에 둡니다
App Router 경계
app/은 얇게 유지합니다. 주요 책임은 다음과 같습니다.
(marketing),(auth),(onboarding)같은 레이아웃 기반 라우트 그룹화app/admin/layout.tsx같은 서버 레이아웃에서 접근 제어 강제app/api/[...path]/route.ts같은 라우트 핸들러 노출- 라우트 트리 가까이에 로딩/에러 바운더리 정의
대부분의 비즈니스 로직은 페이지 안에 직접 구현하지 않고 src/에서
가져옵니다. 대부분의 인증된 page.tsx 파일도 얇게 유지되며, 실제 렌더링은
클라이언트 feature island에 넘깁니다. 서버 경계는 인증, 메타데이터, 셸
관심사에 더 많이 사용되며, 인증된 모든 페이지에 SSR prefetch를 넓게
적용하는 구조는 아닙니다.
데이터 접근 경계
프론트엔드에는 서로 다른 세 가지 접근 경로가 있습니다.
| 경로 | 사용 목적 | 주요 파일 |
|---|---|---|
/api를 통한 브라우저 fetch | 일반적인 클라이언트 측 제품 요청 | src/shared/api/base.ts, src/shared/api/orval-mutator.ts, app/api/[...path]/route.ts |
| SSR 또는 서버 레이아웃 중의 서버 fetch | 인증 확인과 서버 전용 읽기 | src/shared/api/serverFetch.ts, src/entities/session/api/getServerUser.ts |
| 공개 서버 읽기 | 테넌트 홈페이지와 공개 학원 조회 | 라우트 페이지에서 사용하는 엔티티 수준 serverFetch 헬퍼 |
브라우저는 항상 ENV.API_URL을 대상으로 하며, 이 값은 /api로
하드코딩되어 있습니다. 이렇게 하면 인증 쿠키 처리, 테넌트 헤더 전달,
refresh 재시도, 오류 정규화가 모두 프론트엔드가 제어하는 하나의 경계에
모입니다.
소스 경로
| 계약 | 소스 경로 |
|---|---|
| 라우트 그룹, 보호 레이아웃, 라우트 핸들러 | lumie-frontend/app/** |
| 동일 출처 프록시 | lumie-frontend/app/api/[...path]/route.ts |
| 브라우저 fetch 기본 요소 | lumie-frontend/src/shared/api/base.ts, lumie-frontend/src/shared/api/orval-mutator.ts |
| 서버 인증 fetch | lumie-frontend/src/entities/session/api/getServerUser.ts, lumie-frontend/src/shared/api/serverFetch.ts |
| query client 수명주기 | lumie-frontend/src/shared/lib/query-client.ts, lumie-frontend/src/shared/providers/QueryProvider.tsx |
| 공유 API 코드용 세션 사이드 채널 | lumie-frontend/src/shared/api/sessionAccessor.ts, lumie-frontend/src/shared/lib/sessionCache.ts |
query 및 세션 아키텍처
TanStack Query는 QueryProvider에서 한 번만 초기화됩니다. getQueryClient()는
다음을 생성합니다.
- 서버에서는 요청별 query client를 생성해 요청 간 캐시 누수를 막습니다
- 브라우저에서는 클라이언트 내비게이션 동안 유지되는 단일 query client를 생성합니다
세션 상태는 의도적으로 분리되어 있습니다.
getServerUser()는 서버 레이아웃에서 라우트 보호를 담당합니다useMe()와useMeQuery()는 클라이언트 코드에서 인증된 사용자를 노출합니다sessionAccessor와sessionCache는 공유 API 코드가 FSD 레이어 규칙을 깨지 않고 테넌트 세션 데이터를 읽거나 지울 수 있게 해줍니다
UI 조합
주요 셸은 전역 단일 컴포넌트가 아니라 widget 또는 라우트 로컬 셸로 구성합니다.
- 마케팅 페이지는
app/(marketing)/layout.tsx를 사용합니다 - 관리자 페이지는
AdminShell을 사용합니다 - 대시보드 페이지는
StudentSidebar,Header, 공유 사이드바 기본 요소를 사용합니다
이렇게 하면 라우트별 레이아웃 선택은 해당 라우트 그룹 가까이에 두고,
재사용 가능한 제품 로직은 src/ 안에 남겨둘 수 있습니다.
검증
cd lumie-frontend
rg -n "getServerUser|serverFetch|sessionAccessor|sessionCache|getQueryClient|QueryProvider" \
app src
npx tsc --noEmit
성공 기준은 grep이 라우트 보호, 서버 fetch, 세션 캐시, query client
경계를 찾아내고, npx tsc --noEmit가 TypeScript 오류 없이 완료되는
것입니다.