Frontend Tech Stack Convention
기술스택
항목 선택
언어 TypeScript 5+ (strict mode)
UI 프레임워크 React 19+
메타 프레임워크 Next.js 16+ (App Router)
UI 컴포넌트 shadcn/ui + Radix UI
스타일링 Tailwind CSS v4
빌드 도구 Vite
테스트 Vitest + React Testing Library
패키지 매니저 pnpm
린팅 ESLint + Prettier
프로젝트 구조 (Next.js App Router)
src/ ├── app/ # App Router 라우트 │ ├── layout.tsx # Root layout │ ├── page.tsx # Home page │ ├── globals.css # Global styles (Tailwind) │ ├── (auth)/ # Route group │ │ ├── login/page.tsx │ │ └── signup/page.tsx │ └── api/ # API Route Handlers │ └── [resource]/route.ts ├── components/ │ ├── ui/ # shadcn/ui 컴포넌트 (자동 생성) │ ├── common/ # 공용 컴포넌트 │ └── [feature]/ # 기능별 컴포넌트 ├── hooks/ # 커스텀 훅 ├── lib/ # 유틸리티, 헬퍼 │ ├── utils.ts # cn() 등 공용 유틸 │ └── api.ts # API 클라이언트 ├── types/ # 전역 타입 정의 ├── stores/ # 상태 관리 (Zustand) ├── constants/ # 상수 정의 └── tests/ # 테스트 파일 (co-location 권장)
TypeScript 컨벤션
-
strict: true 필수
-
interface 로 객체 타입 정의 (확장 가능), type 은 유니온/인터섹션에 사용
-
any 사용 금지 → unknown
- type guard 사용
-
컴포넌트 Props는 interface 로 정의, React.FC 사용하지 않음
-
as 타입 단언 최소화, 타입 가드 함수 선호
-
enum 대신 as const 객체 사용
// Props 정의 interface ButtonProps { variant?: "default" | "destructive" | "outline"; size?: "sm" | "md" | "lg"; children: React.ReactNode; onClick?: () => void; }
// as const 패턴 const STATUS = { IDLE: "idle", LOADING: "loading", ERROR: "error", } as const; type Status = (typeof STATUS)[keyof typeof STATUS];
React 컨벤션
-
함수 선언문으로 컴포넌트 정의 (function Component() , 화살표 함수도 허용)
-
Props destructuring 사용
-
단일 책임 원칙: 컴포넌트는 하나의 역할만 담당
-
조건부 렌더링: 삼항 연산자 또는 && (falsy 값 주의)
-
이벤트 핸들러: handle[Event] 네이밍 (예: handleClick , handleSubmit )
-
커스텀 훅: use[Name] 네이밍, 단일 관심사
-
key prop에 index 사용 금지 (안정적인 고유 ID 사용)
function UserCard({ name, email, onEdit }: UserCardProps) { const handleEditClick = () => onEdit(email);
return ( <Card> <CardHeader> <CardTitle>{name}</CardTitle> </CardHeader> <CardContent> <p className="text-muted-foreground">{email}</p> <Button variant="outline" onClick={handleEditClick}> Edit </Button> </CardContent> </Card> ); }
Next.js App Router 규칙
-
Server Component 기본, 필요 시 "use client" 선언
-
"use client" 는 가능한 트리의 말단에 배치 (클라이언트 경계 최소화)
-
데이터 페칭: Server Component에서 async/await 로 직접 fetch
-
Server Actions: "use server" 함수로 폼 처리, 뮤테이션
-
loading.tsx , error.tsx , not-found.tsx 활용
-
Metadata: generateMetadata() 또는 정적 metadata 객체
-
Route Handler: GET , POST 등 named export 함수
-
동적 라우트: [param] 폴더, generateStaticParams() 로 정적 생성
-
Parallel Routes(@slot ), Intercepting Routes((.) ) 적절히 활용
shadcn/ui 규칙
-
npx shadcn@latest add [component] 로 컴포넌트 추가
-
components/ui/ 에 생성된 코드를 프로젝트 요구에 맞게 커스터마이즈
-
cn() 유틸리티로 조건부 클래스 합성
-
Radix UI primitives 기반으로 접근성 내장
-
테마 커스터마이즈는 globals.css 의 CSS 변수로 관리
import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button";
<Button className={cn("w-full", isActive && "bg-primary")} variant="outline"> Click </Button>
Tailwind CSS v4 규칙
-
유틸리티 클래스 우선, 커스텀 CSS 최소화
-
@apply 사용 최소화 (컴포넌트 추출 선호)
-
반응형: sm: , md: , lg: , xl: , 2xl: 접두사 (mobile-first)
-
다크 모드: dark: 접두사 + CSS 변수 기반 테마
-
clsx 또는 cn() (tailwind-merge)로 동적 클래스 합성
상태 관리
-
로컬 상태: useState , useReducer
-
서버 상태: TanStack Query (React Query)
-
전역 클라이언트 상태: Zustand
-
URL 상태: useSearchParams , nuqs
-
폼 상태: React Hook Form + Zod 유효성 검사
파일 네이밍 규칙
대상 규칙 예시
컴포넌트 PascalCase UserProfile.tsx
훅 camelCase (use 접두사) useAuth.ts
유틸리티 camelCase formatDate.ts
타입 PascalCase UserTypes.ts
상수 camelCase 파일, UPPER_SNAKE 변수 apiEndpoints.ts
테스트 원본명.test UserProfile.test.tsx
페이지 (App Router) 소문자 폴더 app/dashboard/page.tsx
Import 순서
// 1. React/Next.js import { useState } from "react"; import Link from "next/link";
// 2. 서드파티 라이브러리 import { useQuery } from "@tanstack/react-query"; import { z } from "zod";
// 3. 내부 컴포넌트 (shadcn/ui 포함) import { Button } from "@/components/ui/button"; import { UserCard } from "@/components/user/UserCard";
// 4. 훅, 유틸, 타입 import { useAuth } from "@/hooks/useAuth"; import { cn } from "@/lib/utils"; import type { User } from "@/types";