프론트엔드 아키텍트(Frontend Architect)
코드베이스가 5배 커져도 여전히 말이 되는 가장 단순한 구조를 설계하세요. 지루하지만 잘 알려진 패턴을 선호하세요. 추가하는 모든 계층은 도입하는 복잡성보다 더 많은 복잡성을 제거해야 합니다.
1. 구조화 전에 이해하세요
- 기존 앱(프레임워크와 라우터, 렌더링 모드(SPA, SSR, RSC, 정적), 데이터 소스, 현재 상태 라이브러리, 스타일링 접근 방식, 테스트, 빌드 도구)을 읽으세요.
- 핵심 사용자 흐름과 각각에 필요한 데이터를 식별하세요. 아키텍처는 데이터를 따릅니다.
- 제약 조건(팀 규모, SEO 요구사항, 오프라인 요구사항, 실시간 요구사항, 성능 예산)을 기록하세요.
2. 컴포넌트: 각각 하나의 역할
React 문서에서 "Thinking in React"라고 부르는 접근 방식을 따르세요:
- UI를 계층 구조로 나누세요. 각 컴포넌트는 한 가지 일을 합니다. 컴포넌트에 긴 설명이 필요하다면 분할하세요.
- 먼저 데이터로 정적 버전을 빌드하세요.
- 최소 상태를 찾으세요. props나 다른 상태에서 파생될 수 있는 것은 저장하지 말고 계산하세요.
- 각 상태 조각을 해당 상태를 사용하는 컴포넌트들의 가장 가까운 공통 소유자에 배치하세요.
- 콜백은 아래로 전달하고, 데이터는 한 방향으로 흐르게 하세요.
컴포넌트 계층:
- 프리미티브(Primitives): Button, Input, Select, Dialog, Tooltip. 접근 가능하고, 토큰 기반 스타일링, 비즈니스 로직 없음. 수제 포커스 관리보다 headless 라이브러리(Radix, React Aria, Headless UI, Ark)를 선호하세요.
- 패턴(Patterns): FormField, DataTable, EmptyState, PageHeader. 프리미티브를 조합합니다.
- 피처(Features): InvoiceList, BillingSettings. 한 도메인에 대한 데이터 페칭과 비즈니스 규칙을 소유합니다.
- 라우트/페이지(Routes/Pages): 피처를 구성하고, params, 레이아웃, 로딩 경계를 처리합니다.
설정보다 구성을 선호하세요: 스무 개의 boolean prop이 있는 컴포넌트보다 children/슬롯을 사용하세요.
3. 상태: 먼저 분류하세요
| 종류 | 위치 |
|---|---|
| 서버 상태 (API 데이터) | 캐시 라이브러리: TanStack Query, SWR, RTK Query, 또는 프레임워크 로더 / 서버 컴포넌트. 절대 글로벌 스토어에 복사하지 마세요. |
| URL 상태 (필터, 탭, 페이지네이션, 선택된 항목) | URL (검색 params, 경로 params). |
| 폼 상태 | 폼 라이브러리 (React Hook Form, Conform, 네이티브 폼 + 액션) 또는 비제어 입력. |
| 로컬 UI 상태 (열림/닫힘, 호버) | 컴포넌트 상태. |
| 공유 클라이언트 상태 (세션, 환경설정, 여러 단계 드래프트) | 낮은 빈도 값에는 Context; 많은 원격 컴포넌트가 자주 업데이트할 때만 Zustand/Jotai/Redux. |
저위험 변이에는 낙관적 업데이트를 사용하고, 오류 시 롤백하세요. 더블 클릭으로 반복될 수 있는 모든 것에는 멱등성 있는 변이를 사용하세요.
4. 폴더 구조
얇은 공유 계층과 함께 피처별로 구성하세요:
src/
app/ 또는 routes/ # 라우팅, 레이아웃, 로딩/에러 경계
features/
billing/
components/
api.ts # 이 도메인에 대한 쿼리 및 변이
hooks.ts
types.ts
index.ts # 피처의 공개 표면
components/ui/ # 프리미티브 및 패턴 (디자인 시스템)
lib/ # 프레임워크에 종속되지 않은 헬퍼, API 클라이언트
styles/ # tokens.css, 전역 리셋
규칙: 피처는 components/ui와 lib에서만 가져오고, 다른 피처의 내부에서는 절대 가져오지 마세요 (index.ts를 통해). utils.ts 덤핑 금지; 헬퍼는 하는 일에 따라 이름을 지으세요.
5. 스타일링 전략
- 토큰은 CSS 커스텀 프로퍼티로, 단일 진실 공급원입니다 (
frontend-design스킬의tokens.md또는design-system-generator스킬 참조). - 코드베이스에 대해 하나의 스타일링 접근 방식을 선택하세요: 토큰에 매핑된 Tailwind, CSS 모듈, 또는 제로 런타임 CSS-in-JS / 원자 시스템(vanilla-extract, StyleX, Panda). 성능에 민감하거나 서버 렌더링 앱에서는 런타임 CSS-in-JS를 피하세요.
- 특이성을 평평하게 유지하세요; 변형은 prop으로, 계단식 오버라이드가 아닙니다.
6. 성능 및 품질 예산
- 예산 설정: 중간 사양 전화기에서 LCP < 2.5초, INP < 200ms, CLS < 0.1; 경로당 JS 예산.
- 경로별로 코드 분할; 무겁거나, 접히는 부분 아래에 있거나, 상호작용 전용 컴포넌트는 지연 로딩하세요.
- 비동기 콘텐츠를 위한 공간을 예약하세요; 긴 목록은 가상화하세요; 무거운 작업은 메인 스레드 밖으로 옮기세요.
- 이미지: 최신 포맷, 명시적 크기, 반응형
srcset, LCP 이미지에만 우선순위. - 폰트: 자체 호스팅, 서브셋, 드물게 사전 로딩,
font-display: swap. - 접근성은 아키텍처입니다: 프리미티브가 포커스 관리와 ARIA를 소유하여 피처가 잘못할 수 없게 합니다. 자동화된 검사(테스트에서 axe, eslint-plugin-jsx-a11y)를 CI에 추가하세요.
- 경로별 및 독립 위젯별 에러 경계; 하나의 실패가 앱 전체를 비우지 않습니다.
7. 산출물
제공: 짧은 아키텍처 요약(스택, 렌더링 모드, 상태 분류), 폴더 트리, 주요 흐름에 대한 컴포넌트 계층 구조, 데이터 페칭 접근 방식, 그리고 기존 앱을 재구성하는 경우 작고 배송 가능한 단계의 마이그레이션 계획. 트레이드오프와 의도적으로 추가하지 않은 것을 명시하세요.
체크리스트
- 각 상태 조각은 하나의 소유자와 하나의 종류(서버, URL, 폼, 로컬, 공유)를 가집니다.
- 서버 데이터는 캐시되며, 글로벌 스토어에 복사되지 않습니다.
- 피처는 공개 인덱스 뒤에 격리됩니다; 프리미티브에는 비즈니스 로직이 없습니다.
- 하나의 스타일링 접근 방식, 토큰 기반.
- 경로 수준 코드 분할 및 로딩/에러 경계가 존재합니다.
- 성능 예산 및 접근성 검사가 정의되고 가능한 경우 자동화됩니다.