Next.js 모범 사례
Next.js App Router 개발을 위한 원칙.
1. 서버 컴포넌트 vs 클라이언트 컴포넌트
결정 트리
다음이 필요합니까...?
│
├── useState, useEffect, 이벤트 핸들러
│ └── 클라이언트 컴포넌트 ('use client')
│
├── 직접 데이터 페칭, 상호작용 없음
│ └── 서버 컴포넌트 (기본값)
│
└── 둘 다 필요?
└── 분할: 서버 부모 + 클라이언트 자식
기본값
| 유형 | 사용처 |
|---|
| 서버 | 데이터 페칭, 레이아웃, 정적 콘텐츠 |
| 클라이언트 | 폼, 버튼, 대화형 UI |
2. 데이터 페칭 패턴
페치 전략
| 패턴 | 사용처 |
|---|
| 기본값 | 정적 (빌드 시 캐시됨) |
| Revalidate | ISR (시간 기반 갱신) |
| No-store | 동적 (매 요청마다) |
데이터 흐름
| 소스 | 패턴 |
|---|
| 데이터베이스 | 서버 컴포넌트 fetch |
| API | 캐싱이 있는 fetch |
| 사용자 입력 | 클라이언트 상태 + 서버 액션 |
3. 라우팅 원칙
파일 규칙
| 파일 | 용도 |
|---|
page.tsx | 라우트 UI |
layout.tsx | 공유 레이아웃 |
loading.tsx | 로딩 상태 |
error.tsx | 에러 경계 |
not-found.tsx | 404 페이지 |
라우트 구성
| 패턴 | 사용처 |
|---|
라우트 그룹 (name) | URL 변경 없이 구성 |
병렬 라우트 @slot | 동일 레벨의 여러 페이지 |
가로채기 (.) | 모달 오버레이 |
4. API 라우트
라우트 핸들러
| 메서드 | 사용처 |
|---|
| GET | 데이터 읽기 |
| POST | 데이터 생성 |
| PUT/PATCH | 데이터 업데이트 |
| DELETE | 데이터 삭제 |
모범 사례
- Zod로 입력 유효성 검사
- 적절한 상태 코드 반환
- 오류를 우아하게 처리
- 가능하면 Edge 런타임 사용
5. 성능 원칙
이미지 최적화
- next/image 컴포넌트 사용
- 접히는 영역 위의 이미지에는 priority 설정
- 흐릿한 플레이스홀더 제공
- 반응형 크기 사용
번들 최적화
- 무거운 컴포넌트는 동적 임포트
- 라우트 기반 코드 분할 (자동)
- 번들 분석기로 분석
6. 메타데이터
정적 vs 동적
| 유형 | 사용처 |
|---|
| 정적 내보내기 | 고정 메타데이터 |
| generateMetadata | 라우트별 동적 생성 |
필수 태그
- title (50-60자)
- description (150-160자)
- Open Graph 이미지
- 표준 URL (Canonical URL)
7. 캐싱 전략
캐시 계층
| 계층 | 제어 |
|---|
| 요청 | fetch 옵션 |
| 데이터 | revalidate/tags |
| 전체 라우트 | 라우트 설정 |
재검증
| 방법 | 사용처 |
|---|
| 시간 기반 | revalidate: 60 |
| 요청 시 | revalidatePath/Tag |
| 캐시 없음 | no-store |
8. 서버 액션
사용 사례
모범 사례
- 'use server'로 표시
- 모든 입력 유효성 검사
- 타입이 있는 응답 반환
- 오류 처리
9. 안티패턴
| ❌ 하지 말 것 | ✅ 할 것 |
|---|
| 모든 곳에 'use client' | 기본적으로 서버 사용 |
| 클라이언트 컴포넌트에서 fetch | 서버에서 fetch |
| 로딩 상태 생략 | loading.tsx 사용 |
| 에러 경계 무시 | error.tsx 사용 |
| 큰 클라이언트 번들 | 동적 임포트 사용 |
10. 프로젝트 구조
app/
├── (marketing)/ # 라우트 그룹
│ └── page.tsx
├── (dashboard)/
│ ├── layout.tsx # 대시보드 레이아웃
│ └── page.tsx
├── api/
│ └── [resource]/
│ └── route.ts
└── components/
└── ui/
기억하세요: 서버 컴포넌트는 이유가 있어 기본값입니다. 거기서 시작하고, 필요한 경우에만 클라이언트를 추가하세요.