React 프론트엔드
모든 React 프론트엔드를 위한 규칙입니다. 이 파일은 항상 적용되는 내용을 담고 있습니다. 해당 주제를 작업하기 전에 일치하는 참조 문서를 읽으십시오.
| 작업 | 읽을 문서 |
|---|---|
| 폴더, 레이어, 슬라이스, public API, 임포트, 배럴 파일 | references/structure.md |
| FSD 심화: 결정 트리, 교차 임포트, 엔티티, 에셋, 인증/API 배치, 프레임워크 연동, 마이그레이션 | references/fsd/ (structure.md 끝에 색인) |
| 네이밍, 파일 크기, props, 디자인 시스템, 레이아웃, 상수 | references/code-style.md |
| 컴포넌트, 훅, 이펙트, 메모이제이션, 트랜지션, 긴 리스트, 하이드레이션 | references/hooks-and-rendering.md |
| 워터폴, 번들 크기, 코드 스플리팅, 프리로딩, 리렌더링 비용 | references/performance.md |
| 서버 데이터, 쿼리, 뮤테이션, 로더, 검색 파라미터, Zustand | references/data-and-state.md |
| 폼, 유효성 검사, 폼 오류 | references/forms.md |
| 시맨틱, 키보드, 포커스, 대비, 라이브 리전, 접근성 테스트 | references/accessibility.md (React 구현) + frontend-accessibility 스킬 (전체 규칙, 감사) |
| 단위, 컴포넌트 및 E2E 테스트, 커버리지, API 모킹 | references/testing.md |
| 스토리, Storybook 접근성 검사, 시각적 회귀 | references/storybook.md |
| XSS, HTML/Markdown/LLM 출력 렌더링, 인증, CSRF, CSP, 환경 변수, 로그아웃 | references/security.md |
| Node 버전, 패키지 매니저, pre-commit + gitleaks, 의존성 감사 | references/tooling.md |
번역, 언어 전환, <html lang> | references/i18n.md |
| 새 프로젝트 설정 | references/setup.md |
| 프론트엔드 컨테이너 이미지 | references/docker.md + docker-kubernetes 스킬 |
스택
- React 19 (이벤트 이펙트와
<Activity>를 위해 ≥ 19.2) · TypeScript strict - 렌더링: Vite SPA 또는 React Router 프레임워크 모드 (SSR + 하이드레이션). React Router 프레임워크 전용으로 표시된 규칙은 해당 모드에만 적용됩니다. Next.js, React Server Components, SWR은 사용하지 않습니다.
- 라우팅: TanStack Router 또는 React Router — 프로젝트별로 고정, 코드에서 감지하여 혼용하지 않습니다.
- 서버 상태: TanStack Query · 클라이언트 상태: Zustand
- 폼: React Hook Form + Zod 또는 TanStack Form + Zod — 프로젝트별로 고정
- 유효성 검사: 모든 경계(API 응답, 폼, 검색 파라미터, 환경 변수, 스토리지,
postMessage)에서 Zod 사용 - 도구: Biome (린트 + 포맷), Steiger (FSD 규칙), Vitest + Testing Library, Playwright, Storybook
- React Compiler 없음 → 메모이제이션은 수동으로, 단 측정 후에만 적용합니다.
구조 — Feature-Sliced Design
레이어는 app → pages → widgets → features → entities → shared 순서로 구성됩니다. 임포트는 항상 아래 방향으로만 가능하며, 같은 레이어의 슬라이스 간 임포트는 금지됩니다. 모든 슬라이스는 명시적인 named exports (export * 금지)가 있는 index.ts를 통해서만 임포트됩니다. shared/ui와 shared/lib는 컴포넌트/라이브러리당 하나의 index 파일을 가집니다. app, pages, shared로 시작하며, 빈 레이어 폴더는 만들지 않습니다. 코드는 추출 규칙이 충족될 때까지 페이지에 남아 있습니다. 즉, 현재 여러 곳에서 사용되고, 단일 소비자와 무관하게 변경할 이유가 있으며, 집중된 책임을 가질 때 추출합니다. 세 조건이 모두 충족되지 않으면 중복을 허용합니다. widgets는 권장되지 않습니다: 여러 페이지에서 재사용되는 복합 블록으로 페이지에 포함될 수 없는 경우에만 사용합니다(기존 widgets은 유효함). 자세한 내용: references/structure.md, 상위 규칙서 references/fsd/rules.md.
- 파일당 하나의 컴포넌트, 파일당 하나의 훅, 컴포넌트당 약 150줄; 컴포넌트 이름에는 슬라이스 이름을 접두사로 붙입니다 (
references/code-style.md). - 디자인 시스템 우선:
shared/ui/의 자체 컴포넌트를 사용합니다.shared/ui/외부에서는shared/ui/primitives/(UI 라이브러리 CLI 출력물, 직접 수정 금지)를 임포트하지 않습니다. 디자인 시스템 컴포넌트, props, 토큰이 할 수 있는 것을 확인하기 전에 새로운 CSS를 추가하지 않습니다.
컴포넌트와 훅 — 항상
- 컴포넌트를 다른 컴포넌트 내부에 정의하지 마십시오 — 매 렌더링마다 리마운트됩니다.
- 파생, 동기화 금지: props나 state로부터 계산할 수 있는 값은 렌더링 중에 계산합니다. props나 state를 state에 복사하는
useEffect를 사용하지 마십시오.key로 리셋합니다. - 사용자 액션은 이벤트 핸들러에서 실행되며, state 플래그로 트리거된 이펙트에서 실행되지 않습니다.
- 이펙트는 외부 시스템과 동기화할 때만 사용하며, 좁은 원시 의존성, 이펙트당 하나의 책임, 정리(cleanup) 함수를 포함합니다. exhaustive-deps 규칙을 절대 무음 처리하지 마십시오.
setState(prev => …)는 다음 값이 이전 값에 의존할 때 사용합니다.useState(() => expensive())는 초기 값이 비쌀 때 사용합니다.- props나 state를 절대 변이(mutate)하지 마십시오:
toSorted(),toReversed(),toSpliced(),with(), spread 연산자를 사용합니다. {count > 0 ? <X /> : null}— 숫자가 왼쪽에 있는&&는 절대 사용하지 마십시오.useEffect에서fetch를 사용하지 마십시오. 서버 데이터는 TanStack Query 또는 라우트 로더를 통해 가져옵니다.memo/useMemo/useCallback은 프로파일링 후 또는 참조 안정성이 필요한 경우에만 사용합니다.- 오류는 절대 무시하지 않습니다: 빈
catch블록,.catch(() => {})사용 금지. 모든 라우트에는 오류 경계(error boundary)가 있어야 합니다. 모든 쿼리에는 로딩, 오류 및 빈 UI가 있어야 합니다.
데이터와 상태 — 항상
- 요청 함수 → 훅 → 컴포넌트 순서를 따릅니다. HTTP는
shared/api/의 클라이언트를 통해서만 사용합니다. 훅이나 컴포넌트에서 HTTP 클라이언트를 직접 임포트하지 마십시오. - 서버 데이터는 TanStack Query에만 존재하며,
queryOptions를 사용한 쿼리 키 팩토리를 통해 관리됩니다. 응답은 요청 함수에서 Zod로 파싱됩니다. - 일반 리소스 접근, CRUD 및 쿼리 키 팩토리는
shared/api(또는 해당 페이지만 사용하는 경우 해당 페이지/기능)에 있습니다.entities/<x>/api는 도메인 규칙이나 해당 엔티티의 매핑이 필요한 요청에만 사용합니다. - 뮤테이션은 영향을 받는 모든 쿼리를 무효화(업데이트)합니다.
- URL 상태 (필터, 정렬, 페이지네이션, 탭)는 타입이 지정되고 Zod로 유효성 검사된 검색 파라미터에 저장됩니다.
window.location에서 직접 파싱하지 마십시오. - Zustand는 공유 클라이언트 상태에만 사용합니다. 항상 selector와 함께 사용합니다. persisted store는
version,migrate및partialize를 사용하며, 민감한 데이터를 보관하지 않습니다. - 라우트 데이터는 라우트 로더에서 병렬로 로드합니다(
queryClient.query({ ...options, staleTime: 'static' }), 이전에는ensureQueryData라고 불렸습니다). 중첩된 컴포넌트에서 순차적으로 로드하지 마십시오.
접근성 — 내장, 절대 나중에 추가하지 않음
목표: WCAG 2.2 AA. 모든 컴포넌트와 페이지의 완료 정의(definition of done)의 일부입니다. frontend-accessibility 스킬은 완전한 규칙과 감사 절차를 포함합니다. 모든 UI 변경 시 로드하십시오.
- 시맨틱 HTML 우선: 액션에는
<button>, 탐색에는<a>/<Link>, 모든 필드에는<label>, 랜드마크, 하나의<h1>및 순서가 있는 제목(headings)을 사용합니다. ARIA는 기본 네이티브 요소가 적합하지 않은 경우에만 사용합니다. - 키보드: 모든 요소가 키보드로 접근 및 조작 가능해야 합니다. 양수
tabIndex사용 금지. 키보드 트랩 금지. - 모든 상호작용 요소에 가시적 포커스 (
:focus-visible, ≥ 3:1 대비). - 포커스 관리: 라우트 변경 시 페이지 제목을 설정하고 포커스를
<h1>또는<main>으로 이동합니다. 다이얼로그는 포커스를 트랩하고,Escape키로 닫히며, 포커스를 트리거로 되돌립니다. - 이름(name): 아이콘 버튼은
aria-label을 가집니다. 이미지는alt속성을 가집니다 (장식용인 경우alt=""). - 텍스트 대비 4.5:1, UI 컴포넌트는 3:1. 색상만으로 정보를 전달하지 않습니다.
- 움직임 감소(reduced motion)를 존중합니다. 비동기 결과 및 오류에 대해 라이브 리전(
role="status"/role="alert")을 사용합니다. - 테스트는 역할(role)과 접근 가능한 이름(accessible name)으로 질의합니다. 스토리는 접근성 애드온을 통과합니다. 주요 페이지는 Playwright에서 axe를 통과합니다.
테스트 — 기능과 함께 작성, 나중에 작성하지 않음
- Vitest + Testing Library 단위 및 컴포넌트 테스트는 모든 기능에 대해 동일한 변경 사항 내에서 작성합니다. UI를 통해 동작을 테스트합니다 (역할, 레이블,
user-event). 구현 세부 사항을 테스트하지 않습니다. - 라인 커버리지 ≥ 80%, 브랜치 커버리지 ≥ 80%, CI에서 Vitest 임계값으로 적용됩니다.
- Playwright E2E는 모든 중요 사용자 흐름(로그인, 핵심 비즈니스 흐름, 결제/체크아웃, 권한)에 대해 작성합니다. 역할 기반 로케이터 사용, 고정 대기 금지, 재사용된 인증 상태, 주요 페이지의 axe 스캔.
- 모든 공유/재사용 UI 컴포넌트에 대한 Storybook 스토리, 모든 상태(기본, 로딩, 빈, 오류, 비활성화, 긴 내용)를 다룹니다. 기본적으로 데스크톱 뷰포트를 사용하며, 프로젝트에서 반응형 레이아웃이 필요한 경우에만 모바일 뷰포트를 추가합니다.
- Storybook + Playwright 스크린샷을 통한 시각적 회귀 테스트. 스냅샷 테스트는 드물게 그리고 목표를 정해서만 사용합니다.
보안 — 항상
dangerouslySetInnerHTML금지 (DOMPurify 사용 시 예외). React 외부에서는textContent를 사용하고innerHTML을 절대 사용하지 않습니다. 모델 출력 및 Markdown을 원시 HTML로 렌더링하지 않습니다.localStorage/sessionStorage/IndexedDB에 토큰이나 민감 데이터를 저장하지 않습니다. 인증은 백엔드에서 설정한HttpOnly세션 쿠키를 사용합니다. OIDC의 경우 backend-for-frontend 패턴을 선호합니다.- 상태를 변경하는 요청은 CSRF 헤더를 보냅니다.
credentials: 'include'는 자체 API에만 사용합니다. VITE_*변수는 공개입니다 — 절대 비밀 정보를 포함하지 마십시오.?redirect=대상은 동일 출처(same-origin) 상대 경로만 허용합니다 (오픈 리다이렉트 방지).- 로그아웃 시 서버 세션, TanStack Query 캐시 및 모든 persisted store를 지웁니다.
- CSP는 HTTP 헤더로 설정하며
object-src 'none',base-uri 'none',frame-ancestors를 포함합니다. postMessage핸들러는event.origin을 확인합니다. 신뢰할 수 없는 객체를 병합하지 마십시오 (프로토타입 오염).- 프론트엔드는 절대 권한 부여를 강제하지 않습니다. 백엔드가 담당합니다 [ASVS 8.3.1].
TypeScript 및 Biome
strict활성화 (TypeScript 7부터 기본값 — 명시적으로 유지).noUncheckedIndexedAccess,verbatimModuleSyntax,moduleResolution: "bundler",lib는ES2023+DOM을 포함합니다. TypeScript 7에서는types기본값이[]이므로"vite/client"를 명시적으로 나열하십시오.baseUrl은 사라졌으므로paths를 사용합니다 ("@/*": ["./src/*"]).any금지 (unknown을 사용하고 좁혀서 사용), 오류를 무음 처리하기 위한 non-null 단언 금지,@ts-ignore금지 (불가피한 경우@ts-expect-error를 이유와 함께 사용). 매직 넘버나 문자열 금지 —shared/config/또는 해당 슬라이스의config/model에 명명된 상수로 정의합니다.- 외부 데이터의 타입은 Zod 스키마(
z.infer/z.output)에서 가져오며, 수동으로 작성하여 불일치가 발생할 수 있는 인터페이스를 사용하지 않습니다. - Biome: 권장 규칙에
noNestedComponentDefinitions,noReExportAll및noConsole추가 (권장 세트에 포함되지 않음).noBarrelFile은 슬라이스index.ts파일에 대해 비활성화합니다 (references/structure.md). biome ci,tsc --noEmit,steiger ./src및knip이 로컬 및 CI를 통과해야 합니다. 이유를 설명하는 주석 없이 인라인 규칙을 비활성화하지 마십시오.- 주석은 영어로 작성합니다. TODO는 프로젝트의 TODO 마커를 사용합니다.
저장소 위생 — 항상
- Node 버전 고정:
.nvmrc(LTS),package.json의engines와engine-strict, 정확한 npm/pnpm 버전의packageManager. lockfile은 커밋됩니다. - pre-commit을 commit 및 push에 대해 설치합니다: staged 변경 사항과 모든 푸시되지 않은 커밋에 대한 gitleaks, Biome. push 시
tsc, 관련 Vitest 테스트 및 의존성 감사.--no-verify로 절대 우회하지 마십시오. - CI에서 모든 MR 및 정기적인 일정에 따라 의존성 감사를 실행합니다.
자세한 내용: references/tooling.md.
Docker 및 배포
컨테이너 이미지, .dockerignore, Compose 및 Kubernetes는 docker-kubernetes 스킬에서 다룹니다. 프론트엔드 워크플로 및 체크리스트는 references/docker.md에 있습니다. 모든 이미지에는 .dockerignore가 있습니다. 해당 스킬에서 존중해야 할 프론트엔드 특화 사항:
VITE_*값은 빌드 시 번들에 포함됩니다. 여러 환경을 위한 단일 이미지에는 런타임 구성이 필요합니다: 컨테이너 시작 시 환경 변수에서 작성되어 앱 시작 시 가져와 유효성 검사되는config.json(또는config.js)을 제공합니다. 여전히 공개 정보이며 비밀 정보를 포함하지 않습니다.- Vite SPA는 정적 파일입니다: 클라이언트 측 라우트를 위해
index.html로 폴백하는 웹 서버에서 제공하며, 해시된 에셋은 장기 캐시,index.html은no-cache,references/security.md의 보안 헤더를 적용합니다. - React Router 프레임워크 모드 (SSR) 는 런타임에 Node 서버가 필요합니다 (
react-router-serve또는 사용자 정의 서버). 정적 배포가 아닙니다.
작성 후
결과에 대한 보안 검토를 위해 appsec-review 스킬을 제안하십시오.
출처
Vercel Engineering의 React 모범 사례(github.com/vercel-labs/agent-skills, react-best-practices, MIT 라이선스) — Next.js에서 Vite/React Router + TanStack Query로 적용; 추적성을 위해 규칙 ID 유지 — 및 OWASP Secure Agent Playbook (CC BY 4.0; OWASP ASVS 5.0)의 내용을 결합했습니다. 구조는 Feature-Sliced Design (feature-sliced.design, fsd.how)을 따릅니다. references/fsd/는 공식 FSD 에이전트 스킬 (github.com/feature-sliced/skills, MIT 라이선스)에서 적용되었습니다. 접근성 목표: WCAG 2.2.