Next.js 웹 앱 코딩 규칙
요청된 작업에 이 규칙들을 적용하세요. 명시적인 사용자 지시사항은 이 스킬의 모든 규칙이나 기본값보다 우선합니다. 기존 제품 요구사항과 통합을 유지하세요. 이 스킬을 적용한다고 해서 관련 없는 마이그레이션이나 모든 선택적 종속성을 설치하는 것이 허용되지는 않습니다.
구현 전에 프로젝트 지침, 설치된 버전, 기존 규칙을 검사하세요. 사용 가능한 경우 설치된 Next.js 문서(특히 node_modules/next/dist/docs/)를 참조하고, 그렇지 않으면 버전에 맞는 공식 문서를 참조하세요. 다른 버전의 프레임워크 API를 가정하지 마세요.
기본값 및 조건부 기능
| 영역 | 기본값 | 재정의 또는 활성화 규칙 |
|---|---|---|
| 프레임워크 | Next.js App Router 및 strict TypeScript | 프로젝트가 선택한 버전과 명시적인 사용자 선택을 존중합니다. |
| 컴포넌트 | Tailwind CSS를 사용하는 Base UI, 로컬 컴포넌트로 래핑됨 | Radix, shadcn/ui 또는 요청된 다른 라이브러리를 지원합니다. 동일한 컴포넌트 조직을 유지합니다. |
| 변형 | class-variance-authority 및 Tailwind 인식 클래스 병합 헬퍼 | 기존에 확립된 동등한 것을 재사용합니다. |
| API 전송 | 사용자 정의 APIClient 및 ServerAPIClient 추상화 뒤에 있는 Axios | 기존 사용자 정의 클라이언트, fetch 또는 요청된 다른 전송 방식이 유효합니다. 계약을 전송 방식과 독립적으로 유지합니다. |
| 유효성 검사 | 추론된 TypeScript 타입이 있는 Zod 스키마 | 기본적으로 외부 데이터 경계에서 필요합니다. 명시적인 사용자 지시에 의해서만 변경합니다. |
| 원격 클라이언트 데이터 | 공유 쿼리 래퍼가 있는 TanStack Query | 요청된 동등한 것을 재사용합니다. |
| 공유 대화형 상태 | Zustand | 하나의 컴포넌트가 소유한 상태에는 로컬 React 상태를 사용합니다. 명시적인 대안을 존중합니다. |
| 인증 | 인증이 요청될 때 제공자 중립적인 이메일 OTP 흐름 | 선택한 제공자와 요청된 로그인 방법을 따릅니다. 제공자를 암시적으로 선택하거나 프로비저닝하지 마세요. |
| 탭 간 동기화 | 관련 공유 뮤테이션 이벤트에 대해 활성화됨 | 요청 시 선택 사항이며 제거 가능합니다. 탭 간 일관성이 유용한 경우에만 이벤트를 추가합니다. |
| 로깅 및 추적 | 기존 로깅 시스템을 따릅니다. | 존재하지 않는 경우, 요청되지 않는 한 로깅/추적 인프라를 도입, 제안 또는 언급하지 마세요. |
| 모의 데이터 및 시뮬레이션 | 꺼짐 | 사용자가 모의 데이터 또는 개발/테스트 시뮬레이션을 요청할 때만 추가합니다. |
| 개발 도구 | 별도의 개발 전용 모듈 | 관련되거나 요청된 경우에만 도구를 추가합니다. 규정된 도구나 오버레이는 없습니다. |
결정이 필요할 때는 기본값을 선택하세요. 각 기본값에 대해 묻지 마세요. 인증, 상태 저장소, 동기화 또는 인프라가 필요하지 않은 기능에 도입하지 마세요.
필수 폴더 구조
새 코드에는 이 구성을 사용하세요. 프로젝트를 빈 자리 표시자로 채우지 말고, 필요한 책임이 생길 때 디렉토리를 만드세요. 기존 프로젝트에서 마이그레이션 요청 없이 관련 없는 파일을 이동하지 마세요.
src/
├── app/ 라우트, 레이아웃, API 엔드포인트
├── components/
│ ├── ui/ 공유 UI 프리미티브
│ ├── standalone/ 재사용 가능한 도메인 컴포넌트
│ ├── pages/<page>/ 페이지별 컴포넌트
│ └── <feature>/ 기능 서브시스템
├── controllers/
│ ├── client/<domain>/ 브라우저 API 호출
│ └── server/<domain>/ 서버 측 API 호출
├── providers/ React 컨텍스트 제공자
├── stores/ Zustand 스토어
├── hooks/ 공유 React 훅
├── helpers/ 인증/액세스 및 기타 워크플로
├── lib/ API 클라이언트, 캐시, 로깅, 동기화
├── db/ 데이터베이스/Redis 클라이언트
├── utils/ 작은 유틸리티 및 상수
└── assets/icons/ 소스 SVG
인프라를 위한 폴더 이름은 배치를 설명할 뿐 필수 서비스를 나타내지 않습니다. 이 트리에 데이터베이스, Redis, 캐시 또는 로깅 스택이 암시되어 있지 않습니다.
- 이러한 책임 폴더 아래에 도메인 코드를 구성하고, 다른 최상위 아키텍처로 대체하지 마세요.
- PascalCase 컴포넌트 파일,
useSomething.ts훅(JSX가 있는 경우에만.tsx),updateName.ts와 같은 camelCase 작업 파일을 사용하세요. - 설명적인 도메인 및 기능 디렉토리를 사용하세요.
page.tsx,layout.tsx,route.ts와 같은 프레임워크 파일 이름은 변경하지 않고 유지하세요. - 컨트롤러 네임스페이스를 조합하려면
index.ts를 사용하고, 다중 파일 컴포넌트의 진입점으로index.tsx를 사용하세요. src에 루트를 둔 가져오기에는@/를 사용하고, 가까운 내부 형제에는 상대 가져오기를 사용하세요. 타입 전용 종속성에는import type을 사용하세요.- 임시 복사본, 백업 폴더, 중복 구현, 책임이 불분명한 포괄 파일을 피하세요.
라우트 및 컴포넌트 합성
app/을 라우팅, 레이아웃 구성, 요청 처리 및 초기 데이터 로딩에 집중하도록 유지하세요. 레이아웃 경계가 유용할 때 (auth), (no-auth), (settings)와 같은 라우트 그룹을 사용하세요. 라우트 그룹 이름만으로는 액세스 보호를 제공하지 않습니다.
페이지를 얇게 유지하세요: 초기 데이터를 로드하고, 페이지 수준 결정을 해결하고, components/pages/<page>/ 또는 관련 기능 디렉토리에서 컴포넌트를 렌더링하세요. Server Component가 기본값입니다. 브라우저 상태, 이벤트 및 API를 위해 클라이언트 경계를 도입하세요.
컴포넌트 배치:
components/ui/: 도메인 독립적인 프리미티브 및 선택한 UI 라이브러리 주변의 래퍼.components/standalone/: 페이지나 기능 전반에 걸쳐 재사용되는 도메인 컨트롤.components/pages/<page>/: 한 페이지에 특화된 컴포넌트.components/<feature>/: 탐색 또는 명령 인터페이스와 같이 라우트 전체에서 공유되는 더 큰 서브시스템.
복잡한 컴포넌트는 집중된 파일로 분할하세요. 이것은 핵심 규칙입니다. 상당한 컴포넌트는 index.tsx 합성 진입점과 필요에 따라 명명된 지원 컴포넌트, 탭, 행, 훅 및 타입이 있는 디렉토리를 가져야 합니다. 하나의 파일에 여러 개의 상당한 서브컴포넌트와 관련 없는 효과를 축적하지 마세요. 임의의 줄 제한보다는 책임에 따라 분할하고, 사소한 마크업을 조각내지 마세요.
상호작용 메커니즘(키보드 탐색, 선택, 이벤트 구독, 드래그 동작, 보기 조정)을 기능-로컬 훅 또는 집중된 핸들러 컴포넌트에 유지하세요. 공유될 때 훅을 src/hooks/로 이동하세요. 입력 단축키가 편집 가능한 컨트롤을 방해하지 않도록 범위를 지정하고, 마운트 해제 시 리스너를 정리하세요.
프리미티브 라이브러리를 로컬로 래핑하고 Dialog, DialogTrigger, DialogContent, DialogTitle과 같은 구성 가능한 부분을 노출하세요. 접근 가능한 의미, 포커스 동작 및 실제 라이브러리의 구성 API를 유지하세요. 호환성을 확인하지 않고 Radix 특정 props를 Base UI 래퍼에 복사하지 마세요.
기본 프리미티브 또는 네이티브 요소에서 래퍼 props의 타입을 지정하고 필요한 사용자 정의 props만 추가하세요. 크기, 강조 및 상태에 대해 타입이 지정된 변형을 사용하세요. 소비자 클래스를 예측 가능하게 병합하고 설치된 React/라이브러리 API에 따라 ref 지원을 유지하세요.
Tailwind 유틸리티에 연결된 시맨틱 CSS 변수를 사용하세요. 테마 값을 중앙 집중화하고 컴포넌트 변형을 로컬로 유지하세요. 이 스킬은 시각적 테마, 간격 척도 또는 제품 디자인이 아닌 스타일링 구성을 규정합니다.
app/providers.tsx에서 관련 컨텍스트 제공자를 구성하고, 구현을 providers/에 유지하세요. 브라우저 전용 제공자는 명시적인 클라이언트 경계 뒤에 두고, 개발 도구 초기화를 일반 제공자 책임과 혼합하지 마세요.
컨트롤러 및 HTTP 클라이언트
controllers/client/<domain>/ 또는 controllers/server/<domain>/ 아래에 파일당 하나의 작업을 사용하세요. index.ts 파일을 통해 중첩된 네임스페이스 객체를 조립하세요. 예:
ClientController.org.members.list()
ClientController.profile.updateName(input)
ServerController.org.info()
컨트롤러는 타입이 지정된 작업과 엔드포인트 구성을 제공합니다. 컴포넌트는 URL이나 전송 구성을 조합하는 대신 컨트롤러를 호출해야 합니다. 컨트롤러는 얇게 유지하고, 재사용 가능한 워크플로 로직은 helpers/에, 인프라는 lib/에 유지하세요.
브라우저 및 서버 HTTP 클라이언트를 분리하세요:
lib/APIClient.ts: 브라우저-안전 전송, 쿼리/본문 직렬화, 응답 처리 및 취소 지원.lib/ServerAPIClient.ts: 서버 전용 전송, 신뢰할 수 있는 기본 URL, 요청 범위의 자격 증명/세션 컨텍스트.- 서버 시크릿과 서버 전용 종속성을 브라우저 가져오기 밖에 유지하세요. 공유 변경 가능한 싱글톤에 사용자별 자격 증명을 절대 보관하지 마세요.
- 서버 컨트롤러가 애플리케이션 엔드포인트를 호출할 때, 필요한 자격 증명만 신뢰할 수 있는 대상으로 전달하세요. 호출자가 제공한 ID 헤더를 인증으로 신뢰하지 마세요.
- 전송 오류, 2xx가 아닌 응답 및 애플리케이션 오류를 일관되게 정규화하세요. 의미 있는 상태/코드 정보를 유지하세요. 실패한 요청을 성공적인 빈 데이터로 바꾸지 마세요.
- 컨트롤러 및 전송 계층을 통해
AbortSignal을 지원하세요. 취소는 오류 알림이나 재시도를 트리거하지 않아야 합니다.
API 스키마, 계약 및 핸들러
Zod 스키마가 입력의 기본 진리 소스입니다. 본문, 쿼리 및 경로 매개변수에 대한 특정 스키마를 정의하고, 형식, 허용된 값, 길이/범위 제한, 선택 사항 및 의도적인 강제 변환을 포함하세요. 외부 값을 사용하기 전에 유효성을 검사하세요. 계약의 대체재로 z.any(), 광범위한 레코드, 캐스트 및 널이 아닌 어설션을 피하세요. 각 경계에 대해 의도적으로 알 수 없는 키를 처리하세요.
기본적으로 메서드별 스키마와 내보낸 요청/쿼리/응답 타입을 라우트와 함께 배치하세요. z.infer로 요청 타입을 추론하고, 명시적인 응답 계약을 정의하고, 런타임 유효성 검사가 필요한 곳에 응답 스키마를 사용하세요. 재선언하는 대신 컨트롤러와 컴포넌트에서 이러한 타입을 재사용하세요. 런타임 계약을 브라우저 코드와 공유해야 하거나 라우트 모듈 내보내기 제약 조건이 요구하는 경우, 인접한 schema.ts 또는 types.ts와 타입 전용 가져오기를 사용하여 서버 구현이 클라이언트 번들로 절대 가져와지지 않도록 하세요.
일관된 판별 응답 래퍼를 사용하세요:
type APIResponse<T> =
| { success: true; data: T; error: null }
| {
success: false;
data: null;
error: { code: string; message: string; details?: unknown };
};
사용되는 곳에서 details를 명시적인 안전한 유효성 검사/오류 세부 정보 타입으로 좁히세요. 모든 분기에서 오류와 HTTP 상태를 일관되게 유지하세요. 프로덕션 응답에서 내부 예외나 스택 추적을 노출하지 마세요.
다중 메서드 라우트를 메서드별 스키마/타입 섹션과 get_request 및 post_request와 같은 함수로 구성하세요. lib/api/APIRequestHandler.ts의 withAPIRequestHandler()를 통해 핸들러를 내보내세요.
lib/api/error/를 APIError 클래스와 도메인별로 구성된 타입이 지정된 오류 코드, 메시지 및 상태 카탈로그로 유지 관리하세요. 공유 핸들러는 유효성 검사 실패, 도메인 오류 및 예상치 못한 실패를 동일한 응답 계약에 매핑합니다. 위의 로깅 조건 하에서만 로깅을 통합하세요.
반복적인 조직 조회, ID 확인, 쿼리 구문 분석 및 액세스 워크플로를 helpers/로 추출하세요. 작은 순수 변환 및 상수는 utils/에 유지하세요. 더 큰 통합은 집중된 워크플로 파일로 분할하세요. 라우트는 전체 통합을 포함하기보다는 이러한 단계를 조정해야 합니다.
데이터 가져오기 및 상태
적절한 경우 서버 컨트롤러를 통해 초기 페이지 데이터를 로드하고 타입이 지정된 직렬화 가능한 props를 대화형 컴포넌트에 전달하세요. 독립적인 요청에는 Promise.all을 사용하고, 종속적인 작업에는 순서를 유지하세요.
원격 클라이언트 데이터에는 TanStack Query를 사용하세요. 공유 기본값과 API 래퍼를 풀고, 오류를 전파하며, 쿼리 취소 신호를 전달하는 useApiQuery 래퍼를 제공하세요. 쿼리 키는 관련 ID/리소스/필터 입력을 포함해야 합니다. 서버 측 쿼리 클라이언트의 범위는 요청별로 지정하고, 뮤테이션 후 영향을 받는 쿼리 항목을 무효화하거나 업데이트하세요.
선택, 필터, 패널 및 명령 상태와 같은 공유 대화형 상태에는 기능별 Zustand 스토어를 사용하세요. 개별 슬라이스에 가입하거나 그룹화된 값에는 얕은 선택을 사용하세요. 세터/액션을 명시적으로 유지하고, 소유 리소스가 변경될 때 상태를 재설정하세요. Query가 이미 데이터를 소유하고 있을 때 Zustand에서 원격 데이터의 두 번째 독립적인 복사본을 유지하지 마세요.
컴포넌트 소유 입력, 더티 상태, 유효성 검사 메시지 및 저장 상태에는 로컬 상태를 사용하세요. 중복 제출을 방지하고 실패 시 사용자 입력을 유지하세요. 필수 양식 라이브러리는 없습니다.
대체된 검색/필터 요청을 취소하고 정리 시 중단하세요. 오래된 완료가 새 리소스를 업데이트하는 것을 방지하세요. 실제 취소만 취소로 분류하고, 네트워크 실패를 조용히 삼키지 마세요.
큰 목록의 경우, 유용할 때 가상화 및 페이지 매기기를 적용하고, 로딩, 빈 상태, 오류, 재시도 및 목록 끝 상태를 포함하세요. 리소스/필터가 변경될 때 페이지 매기기 및 선택을 적절히 재설정하세요. 가상화된 행과 일관된 키보드 탐색을 유지하세요.
선택적 탭 간 동기화
사용자가 비활성화하지 않는 한, 관련 공유 뮤테이션에 대해 기본적으로 동기화를 활성화하세요. 동기화 필요 없이 격리된 상태에 추가하지 마세요.
lib/local-sync/에 타입이 지정된 이벤트 레지스트리, events/ 아래에 도메인 이벤트 스키마, 공유 hooks/useLocalSync.ts 훅을 사용하세요. Zod에서 페이로드 타입을 추론하고 나가는 메시지와 들어오는 메시지 모두의 유효성을 검사하세요.
스토리지-이벤트 구현은 다른 탭 및 동일-탭 리스너에게 알리고, 영구적인 애플리케이션 상태 대신 일시적인 이벤트 페이로드를 사용하며, 리스너를 정리해야 합니다. 기존의 동등한 전송이 허용됩니다. 에코 루프 및 중복 처리를 방지하고, 시크릿을 동기화하지 마세요. 이벤트는 표준 상태 소유자를 업데이트하거나 무효화해야 합니다. 탭 간 이벤트는 UI 조정이지 인증이나 보장된 전달이 아닙니다.
인증 및 권한 부여
인증이 범위 내에 있을 때 이 섹션을 적용하세요. 구현을 제공자 중립적으로 유지하고 선택한 서비스 또는 사용자 정의 백엔드에 적용하세요.
- 이메일 OTP 인증의 경우, 코드 보내기, 코드 확인 및 로그아웃 핸들러를 분리하세요.
…
[번역을 위해 잘림]