@choblue/claude-code-toolkit 1.2.6 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 661df17b80c1e5d15cac0feb533ca658c67a5155
1
+ 97eedbf98bcf7cd6a3c3c2e61afd04ae61af9b7c
package/.claude/CLAUDE.md CHANGED
@@ -70,33 +70,74 @@ Step 4: 수정/삭제 → 확인
70
70
 
71
71
  ---
72
72
 
73
- ## 3. 티어별 워크플로우
73
+ ## 3. Task Header (의사결정 가시화)
74
+
75
+ 모든 티어에서 작업 시작 시 Task Header를 출력하여 의사결정을 가시화한다.
76
+
77
+ ### S 티어 헤더
78
+ ```
79
+ 📋 [작업명]
80
+ ⚡ S — [판단 근거 한 줄]
81
+ 📁 [대상 파일]
82
+ ```
83
+
84
+ ### M 티어 헤더
85
+ ```
86
+ 📋 [작업명]
87
+ ⚡ M — [판단 근거 한 줄]
88
+ 📚 [사용할 스킬 목록]
89
+ 🔄 [에이전트 흐름]
90
+ 📁 [예상 파일 수와 대상]
91
+ ```
92
+
93
+ ### L 티어 헤더
94
+ ```
95
+ 📋 [작업명]
96
+ ⚡ L — [판단 근거 한 줄]
97
+ 📚 [사용할 스킬 목록]
98
+ 🔄 [에이전트 흐름]
99
+ 📁 [예상 파일 수와 대상]
100
+ 📌 Plan:
101
+ ○ Step 1: [작업 단위]
102
+ ○ Step 2: [작업 단위]
103
+ ...
104
+ ```
105
+
106
+ L 티어에서는 각 Step 진행에 따라 상태를 갱신한다:
107
+ - `○` 대기 → `▶ ⏳` 진행 중 → `✔` 완료
108
+
109
+ ---
110
+
111
+ ## 4. 티어별 워크플로우
74
112
 
75
113
  ### S 티어 (trivial)
76
114
  Main Agent가 직접 처리한다. 서브에이전트 위임 불필요.
77
- 1. 파일 읽기 → 직접 수정 → 완료
78
- 2. 필요 `git-manager`로 커밋
115
+ 1. **Task Header 출력**
116
+ 2. 파일 읽기 직접 수정 → 완료
117
+ 3. 필요 시 `git-manager`로 커밋
79
118
 
80
119
  ### M 티어 (moderate)
81
120
  TDD/Review를 생략하고 핵심 단계만 수행한다.
82
- 1. **Planning**: 요구사항 정리, 필요 시 `explore`로 탐색
83
- 2. **Implementation**: `code-writer` 에이전트에 구현 위임 (단위별로 나눠 호출)
84
- 3. **Commit**: `git-manager`로 커밋/PR 생성
121
+ 1. **Task Header 출력**
122
+ 2. **Planning**: 요구사항 정리, 필요 `explore`로 탐색
123
+ 3. **Implementation**: `code-writer` 에이전트에 구현 위임 (단위별로 나눠 호출)
124
+ 4. **Commit**: `git-manager`로 커밋/PR 생성
85
125
 
86
126
  ### L 티어 (complex)
87
127
  파일 기반 설계 후 **단위별로** 구현한다.
88
- 1. **Research**: `explore`로 탐색 → `research.md` 작성 (관련 코드 분석, 제약 조건)
89
- 2. **Plan**: `plan.md` 작성 (접근 방식, 변경 파일, 트레이드 오프, **단위별 작업 순서**)
90
- 3. **주석 사이클**: 사용자가 plan.md 메모 반영 **승인 전까지 구현 금지**
91
- 4. **Implementation + Test**: plan.md 단위를 순서대로 `implementer`에 위임 (단위당 1회 호출)
92
- 5. **Review**: `code-reviewer`로 리뷰 `git-manager`로 커밋/PR
128
+ 1. **Task Header 출력**
129
+ 2. **Research**: `explore`로 탐색 → `research.md` 작성 (관련 코드 분석, 제약 조건)
130
+ 3. **Plan**: `plan.md` 작성 (접근 방식, 변경 파일, 트레이드 오프, **단위별 작업 순서**)
131
+ 4. **주석 사이클**: 사용자가 plan.md 메모 반영 **승인 전까지 구현 금지**
132
+ 5. **Implementation + Test**: plan.md의 단위를 순서대로 `implementer`에 위임 (단위당 1회 호출)
133
+ 6. **Review**: `code-reviewer`로 리뷰 → `git-manager`로 커밋/PR
93
134
 
94
135
  ### 풀스택 작업 (FE + BE 동시 변경)
95
136
  티어는 영향도 기준으로 판단하되, 위임 순서는 BE 선행 → FE 후행을 따른다.
96
137
 
97
138
  ---
98
139
 
99
- ## 4. 문서 참조 가이드
140
+ ## 5. 문서 참조 가이드
100
141
 
101
142
  ### Agents (서브에이전트 프롬프트)
102
143
  - `.claude/agents/explore.md` - 코드베이스 탐색 전문가
@@ -108,9 +149,8 @@ TDD/Review를 생략하고 핵심 단계만 수행한다.
108
149
  - `.claude/agents/git-manager.md` - Git 작업 전문가
109
150
 
110
151
  ### Skills (도메인 지식)
111
- - `.claude/skills/Coding/` - 코딩 원칙 및 패턴
112
- - `SKILL.md` - 공통 원칙
113
- - `backend.md` - NestJS 백엔드 규칙
152
+ - `.claude/skills/Coding/SKILL.md` - 공통 코딩 원칙
153
+ - `.claude/skills/NestJS/SKILL.md` - NestJS 백엔드 규칙 (레이어, DTO, DI, 에러 핸들링)
114
154
  - `.claude/skills/React/SKILL.md` - React 컴포넌트, 훅, 상태 관리
115
155
  - `.claude/skills/NextJS/SKILL.md` - Next.js App Router, SSR, Server Actions
116
156
  - `.claude/skills/TailwindCSS/SKILL.md` - Tailwind CSS 유틸리티 패턴
@@ -121,8 +161,8 @@ TDD/Review를 생략하고 핵심 단계만 수행한다.
121
161
  - `.claude/skills/TypeORM/SKILL.md` - TypeORM Entity, Repository, QueryBuilder
122
162
  - `.claude/skills/TDD/` - TDD 테스트 원칙 및 패턴
123
163
  - `SKILL.md` - 공통 TDD 원칙
124
- - `frontend.md` - React 테스트 규칙
125
- - `backend.md` - NestJS 테스트 규칙
164
+ - `references/frontend.md` - React 테스트 규칙
165
+ - `references/backend.md` - NestJS 테스트 규칙
126
166
  - `.claude/skills/DDD/SKILL.md` - DDD 전술적 패턴 (Entity, VO, Aggregate, Repository, Domain Event)
127
167
  - `.claude/skills/Planning/SKILL.md` - 작업 계획 (티어 판단, 작업 분해, 의존성 확인)
128
168
  - `.claude/skills/Git/SKILL.md` - Git 커밋/PR/브랜치 규칙
@@ -143,7 +183,7 @@ TDD/Review를 생략하고 핵심 단계만 수행한다.
143
183
 
144
184
  ---
145
185
 
146
- ## 5. 프로젝트별 오버라이드
186
+ ## 6. 프로젝트별 오버라이드
147
187
 
148
188
  프로젝트 루트에 `CLAUDE.md`가 있으면 이 글로벌 규칙보다 우선한다.
149
189
  프로젝트별 규칙은 글로벌 규칙을 확장하되, 충돌 시 프로젝트 규칙을 따른다.
@@ -12,10 +12,10 @@ INPUT=$(cat)
12
12
 
13
13
  # ─── 1. Quality Gate ───
14
14
  cat << 'EOF'
15
- [Quality Gate] 티어를 판단하고 워크플로우를 따르라.
16
- - S (1-2파일, 단순수정): Main Agent 직접 처리
17
- - M (3-5파일, 명확한 기능): code-writer → git-manager
18
- - L (6+파일, 설계 필요): 프로세스 (TDD 구현 → 리뷰)
15
+ [Quality Gate] 티어를 판단하고 Task Header를 출력한 후 워크플로우를 따르라.
16
+ - S: 📋 📁
17
+ - M: 📋 📚 🔄 📁
18
+ - L: 📋 📚 🔄 📁 📌Plan
19
19
  EOF
20
20
 
21
21
  # ─── 2. Skill Detector ───
@@ -6,9 +6,9 @@ description: 공통 코딩 원칙과 패턴. 코드 작성 시 항상 참조하
6
6
  # Coding Skill - 공통 원칙
7
7
 
8
8
  이 문서는 모든 코드 작성 시 적용되는 공통 원칙을 정의한다.
9
- FE/BE 상세 규칙은 각각의 파일을 참고한다.
10
- - `frontend.md` - React 프론트엔드 규칙
11
- - `backend.md` - NestJS 백엔드 규칙
9
+ FE/BE 상세 규칙은 기술별 스킬에서 다룬다.
10
+ - BE: `../NestJS/SKILL.md`, `../TypeORM/SKILL.md`
11
+ - FE: `../React/SKILL.md`, `../NextJS/SKILL.md`, `../TailwindCSS/SKILL.md`
12
12
 
13
13
  ---
14
14
 
@@ -208,4 +208,5 @@ try {
208
208
  - [ ] 시스템 경계에서 에러 핸들링이 되어 있는가?
209
209
  - [ ] 불필요한 복잡도가 없는가?
210
210
  - [ ] `any` 타입을 사용하지 않았는가?
211
- - [ ] 기존 프로젝트 패턴과 일관성이 있는가?
211
+ - [ ] 기존 프로젝트 패턴과 일관성이 있는가?
212
+
@@ -1,7 +1,12 @@
1
- # Coding Skill - Backend (NestJS)
1
+ ---
2
+ name: nestjs
3
+ description: NestJS 백엔드 개발 가이드. 레이어별 책임, DTO, 에러 핸들링, DI, 네이밍 컨벤션 등 NestJS 코드 작성 시 참조한다.
4
+ ---
5
+
6
+ # NestJS Skill - 백엔드 핵심 규칙
2
7
 
3
8
  NestJS 백엔드 코드에 적용되는 규칙이다.
4
- 공통 원칙은 `SKILL.md`를 함께 참고한다.
9
+ 공통 코딩 원칙은 `../Coding/SKILL.md`를 함께 참고한다.
5
10
 
6
11
  ---
7
12
 
@@ -98,307 +98,7 @@ export function UserTabs() {
98
98
 
99
99
  ---
100
100
 
101
- ## 3. 데이터 페칭
102
-
103
- ### Server Component에서 직접 fetch
104
- - Server Component에서 `async/await`로 직접 데이터를 가져온다
105
- - `useEffect`로 데이터를 가져오지 않는다
106
-
107
- ```typescript
108
- // Good - Server Component에서 직접 페칭
109
- export default async function UserPage({ params }: { params: { id: string } }) {
110
- const user = await getUser(params.id);
111
- return <UserProfile user={user} />;
112
- }
113
- ```
114
-
115
- ### Revalidation 전략
116
- - **Time-based**: `next: { revalidate: 60 }` - 일정 시간마다 갱신
117
- - **On-demand**: `revalidateTag()`, `revalidatePath()` - 특정 이벤트 시 갱신
118
-
119
- ```typescript
120
- // Time-based revalidation
121
- const data = await fetch('https://api.example.com/users', {
122
- next: { revalidate: 3600 }, // 1시간마다 갱신
123
- });
124
-
125
- // Tag-based revalidation
126
- const data = await fetch('https://api.example.com/users', {
127
- next: { tags: ['users'] },
128
- });
129
-
130
- // Server Action에서 revalidation 트리거
131
- 'use server';
132
- import { revalidateTag } from 'next/cache';
133
-
134
- export async function createUser(formData: FormData) {
135
- await saveUser(formData);
136
- revalidateTag('users');
137
- }
138
- ```
139
-
140
- ---
141
-
142
- ## 4. Route Handlers
143
-
144
- ### 위치 및 구조
145
- - `app/api/` 디렉토리 하위에 `route.ts` 파일로 정의한다
146
- - HTTP 메서드를 named export로 정의한다
147
-
148
- ```typescript
149
- // app/api/users/route.ts
150
- import { NextRequest, NextResponse } from 'next/server';
151
-
152
- export async function GET(request: NextRequest) {
153
- const users = await getUsers();
154
- return NextResponse.json(users);
155
- }
156
-
157
- export async function POST(request: NextRequest) {
158
- const body = await request.json();
159
- const user = await createUser(body);
160
- return NextResponse.json(user, { status: 201 });
161
- }
162
- ```
163
-
164
- ### 동적 라우트
165
- ```typescript
166
- // app/api/users/[id]/route.ts
167
- export async function GET(
168
- request: NextRequest,
169
- { params }: { params: { id: string } },
170
- ) {
171
- const user = await getUser(params.id);
172
- if (!user) {
173
- return NextResponse.json({ error: 'Not Found' }, { status: 404 });
174
- }
175
- return NextResponse.json(user);
176
- }
177
-
178
- export async function PUT(
179
- request: NextRequest,
180
- { params }: { params: { id: string } },
181
- ) {
182
- const body = await request.json();
183
- const user = await updateUser(params.id, body);
184
- return NextResponse.json(user);
185
- }
186
-
187
- export async function DELETE(
188
- request: NextRequest,
189
- { params }: { params: { id: string } },
190
- ) {
191
- await deleteUser(params.id);
192
- return new NextResponse(null, { status: 204 });
193
- }
194
- ```
195
-
196
- ---
197
-
198
- ## 5. Middleware
199
-
200
- ### 용도
201
- - 인증/인가 체크
202
- - 리다이렉트 처리
203
- - 요청/응답 헤더 수정
204
- - 국제화(i18n) 라우팅
205
-
206
- ### 작성 패턴
207
- ```typescript
208
- // middleware.ts (프로젝트 루트)
209
- import { NextResponse } from 'next/server';
210
- import type { NextRequest } from 'next/server';
211
-
212
- export function middleware(request: NextRequest) {
213
- const token = request.cookies.get('token')?.value;
214
-
215
- if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
216
- return NextResponse.redirect(new URL('/login', request.url));
217
- }
218
-
219
- return NextResponse.next();
220
- }
221
-
222
- export const config = {
223
- matcher: ['/dashboard/:path*', '/admin/:path*'],
224
- };
225
- ```
226
-
227
- ---
228
-
229
- ## 6. Metadata
230
-
231
- ### 정적 Metadata
232
- ```typescript
233
- // app/about/page.tsx
234
- import type { Metadata } from 'next';
235
-
236
- export const metadata: Metadata = {
237
- title: '소개 - 서비스명',
238
- description: '서비스 소개 페이지입니다.',
239
- };
240
- ```
241
-
242
- ### 동적 Metadata
243
- ```typescript
244
- // app/users/[id]/page.tsx
245
- import type { Metadata } from 'next';
246
-
247
- export async function generateMetadata({
248
- params,
249
- }: {
250
- params: { id: string };
251
- }): Promise<Metadata> {
252
- const user = await getUser(params.id);
253
-
254
- return {
255
- title: `${user.name} - 프로필`,
256
- description: `${user.name}의 프로필 페이지입니다.`,
257
- };
258
- }
259
- ```
260
-
261
- ---
262
-
263
- ## 7. Server Actions
264
-
265
- ### 정의 및 사용
266
- - `'use server'` 지시문으로 서버 액션을 정의한다
267
- - form의 `action` 속성이나 이벤트 핸들러에서 호출한다
268
-
269
- ```typescript
270
- // actions/user.ts
271
- 'use server';
272
-
273
- import { revalidatePath } from 'next/cache';
274
-
275
- export async function createUser(formData: FormData) {
276
- const name = formData.get('name') as string;
277
- const email = formData.get('email') as string;
278
-
279
- await db.user.create({ data: { name, email } });
280
- revalidatePath('/users');
281
- }
282
-
283
- export async function deleteUser(userId: string) {
284
- await db.user.delete({ where: { id: userId } });
285
- revalidatePath('/users');
286
- }
287
- ```
288
-
289
- ### Form에서 사용
290
- ```typescript
291
- // components/CreateUserForm.tsx
292
- import { createUser } from '@/actions/user';
293
-
294
- export function CreateUserForm() {
295
- return (
296
- <form action={createUser}>
297
- <input name="name" type="text" required />
298
- <input name="email" type="email" required />
299
- <button type="submit">생성</button>
300
- </form>
301
- );
302
- }
303
- ```
304
-
305
- ---
306
-
307
- ## 8. 디렉토리 구조 패턴
308
-
309
- ### Route Groups - `(group)`
310
- - URL에 영향을 주지 않고 라우트를 그룹화한다
311
-
312
- ```
313
- app/
314
- (marketing)/
315
- about/page.tsx # /about
316
- blog/page.tsx # /blog
317
- (dashboard)/
318
- layout.tsx # dashboard 전용 레이아웃
319
- settings/page.tsx # /settings
320
- profile/page.tsx # /profile
321
- ```
322
-
323
- ### Private Folders - `_folder`
324
- - 라우팅에서 제외되는 내부 폴더
325
-
326
- ```
327
- app/
328
- _components/ # 라우팅에 포함되지 않음
329
- Header.tsx
330
- Footer.tsx
331
- _lib/ # 내부 유틸리티
332
- utils.ts
333
- ```
334
-
335
- ### Parallel Routes - `@slot`
336
- - 동일 레이아웃에서 여러 페이지를 동시에 렌더링한다
337
-
338
- ```
339
- app/
340
- layout.tsx # children + @analytics + @team 동시 렌더링
341
- @analytics/
342
- page.tsx
343
- @team/
344
- page.tsx
345
- ```
346
-
347
- ### Intercepting Routes - `(.)`, `(..)`, `(...)`
348
- - 현재 레이아웃 내에서 다른 라우트를 가로채서 표시한다
349
-
350
- ```
351
- app/
352
- feed/
353
- page.tsx
354
- (..)photo/[id]/ # /photo/:id를 모달로 가로챔
355
- page.tsx
356
- photo/[id]/
357
- page.tsx # 직접 접근 시 전체 페이지
358
- ```
359
-
360
- ---
361
-
362
- ## 9. Image/Font 최적화
363
-
364
- ### next/image
365
- - 모든 이미지는 `next/image`를 사용한다
366
- - `width`, `height`를 명시하거나 `fill` 속성을 사용한다
367
-
368
- ```typescript
369
- import Image from 'next/image';
370
-
371
- // 크기 지정
372
- <Image src="/hero.png" alt="히어로 이미지" width={800} height={400} />
373
-
374
- // fill 모드 (부모 기준 채움)
375
- <div className="relative h-64 w-full">
376
- <Image src="/banner.png" alt="배너" fill className="object-cover" />
377
- </div>
378
- ```
379
-
380
- ### next/font
381
- - Google Fonts는 `next/font/google`을 사용한다
382
- - 커스텀 폰트는 `next/font/local`을 사용한다
383
-
384
- ```typescript
385
- // app/layout.tsx
386
- import { Inter } from 'next/font/google';
387
-
388
- const inter = Inter({ subsets: ['latin'] });
389
-
390
- export default function RootLayout({ children }: { children: React.ReactNode }) {
391
- return (
392
- <html lang="ko" className={inter.className}>
393
- <body>{children}</body>
394
- </html>
395
- );
396
- }
397
- ```
398
-
399
- ---
400
-
401
- ## 10. 네이밍 컨벤션
101
+ ## 3. 네이밍 컨벤션
402
102
 
403
103
  | 대상 | 규칙 | 예시 |
404
104
  |------|------|------|
@@ -416,7 +116,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
416
116
 
417
117
  ---
418
118
 
419
- ## 11. 금지 사항
119
+ ## 4. 금지 사항
420
120
 
421
121
  - Client Component에서 무거운 데이터 페칭 로직 작성 금지
422
122
  - 불필요한 `'use client'` 선언 금지 - 서버에서 가능하면 서버에서 처리
@@ -426,4 +126,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
426
126
  - `<img>` 태그 직접 사용 금지 - `next/image` 사용
427
127
  - 외부 폰트를 `<link>` 태그로 로드 금지 - `next/font` 사용
428
128
  - `router.push()`를 Server Component에서 사용 금지 - `redirect()` 사용
429
- - API Route에서 비즈니스 로직 직접 구현 금지 - 별도 서비스 레이어로 분리
129
+ - API Route에서 비즈니스 로직 직접 구현 금지 - 별도 서비스 레이어로 분리
130
+
131
+ ---
132
+
133
+ ## 심화 참조
134
+
135
+ | 파일 | 내용 |
136
+ |------|------|
137
+ | `references/data-fetching.md` | 데이터 페칭 (Server Component fetch, Revalidation) + Route Handlers (CRUD, 동적 라우트) |
138
+ | `references/middleware-actions.md` | Middleware (인증, 리다이렉트, matcher) + Server Actions (form action, revalidation) |
139
+ | `references/optimization.md` | Metadata (정적/동적) + 디렉토리 구조 패턴 (Route Groups, Parallel/Intercepting Routes) + Image/Font 최적화 |
@@ -0,0 +1,96 @@
1
+ # 데이터 페칭 & Route Handlers
2
+
3
+ ## 데이터 페칭
4
+
5
+ ### Server Component에서 직접 fetch
6
+ - Server Component에서 `async/await`로 직접 데이터를 가져온다
7
+ - `useEffect`로 데이터를 가져오지 않는다
8
+
9
+ ```typescript
10
+ // Good - Server Component에서 직접 페칭
11
+ export default async function UserPage({ params }: { params: { id: string } }) {
12
+ const user = await getUser(params.id);
13
+ return <UserProfile user={user} />;
14
+ }
15
+ ```
16
+
17
+ ### Revalidation 전략
18
+ - **Time-based**: `next: { revalidate: 60 }` - 일정 시간마다 갱신
19
+ - **On-demand**: `revalidateTag()`, `revalidatePath()` - 특정 이벤트 시 갱신
20
+
21
+ ```typescript
22
+ // Time-based revalidation
23
+ const data = await fetch('https://api.example.com/users', {
24
+ next: { revalidate: 3600 }, // 1시간마다 갱신
25
+ });
26
+
27
+ // Tag-based revalidation
28
+ const data = await fetch('https://api.example.com/users', {
29
+ next: { tags: ['users'] },
30
+ });
31
+
32
+ // Server Action에서 revalidation 트리거
33
+ 'use server';
34
+ import { revalidateTag } from 'next/cache';
35
+
36
+ export async function createUser(formData: FormData) {
37
+ await saveUser(formData);
38
+ revalidateTag('users');
39
+ }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Route Handlers
45
+
46
+ ### 위치 및 구조
47
+ - `app/api/` 디렉토리 하위에 `route.ts` 파일로 정의한다
48
+ - HTTP 메서드를 named export로 정의한다
49
+
50
+ ```typescript
51
+ // app/api/users/route.ts
52
+ import { NextRequest, NextResponse } from 'next/server';
53
+
54
+ export async function GET(request: NextRequest) {
55
+ const users = await getUsers();
56
+ return NextResponse.json(users);
57
+ }
58
+
59
+ export async function POST(request: NextRequest) {
60
+ const body = await request.json();
61
+ const user = await createUser(body);
62
+ return NextResponse.json(user, { status: 201 });
63
+ }
64
+ ```
65
+
66
+ ### 동적 라우트
67
+ ```typescript
68
+ // app/api/users/[id]/route.ts
69
+ export async function GET(
70
+ request: NextRequest,
71
+ { params }: { params: { id: string } },
72
+ ) {
73
+ const user = await getUser(params.id);
74
+ if (!user) {
75
+ return NextResponse.json({ error: 'Not Found' }, { status: 404 });
76
+ }
77
+ return NextResponse.json(user);
78
+ }
79
+
80
+ export async function PUT(
81
+ request: NextRequest,
82
+ { params }: { params: { id: string } },
83
+ ) {
84
+ const body = await request.json();
85
+ const user = await updateUser(params.id, body);
86
+ return NextResponse.json(user);
87
+ }
88
+
89
+ export async function DELETE(
90
+ request: NextRequest,
91
+ { params }: { params: { id: string } },
92
+ ) {
93
+ await deleteUser(params.id);
94
+ return new NextResponse(null, { status: 204 });
95
+ }
96
+ ```
@@ -0,0 +1,74 @@
1
+ # Middleware & Server Actions
2
+
3
+ ## Middleware
4
+
5
+ ### 용도
6
+ - 인증/인가 체크
7
+ - 리다이렉트 처리
8
+ - 요청/응답 헤더 수정
9
+ - 국제화(i18n) 라우팅
10
+
11
+ ### 작성 패턴
12
+ ```typescript
13
+ // middleware.ts (프로젝트 루트)
14
+ import { NextResponse } from 'next/server';
15
+ import type { NextRequest } from 'next/server';
16
+
17
+ export function middleware(request: NextRequest) {
18
+ const token = request.cookies.get('token')?.value;
19
+
20
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
21
+ return NextResponse.redirect(new URL('/login', request.url));
22
+ }
23
+
24
+ return NextResponse.next();
25
+ }
26
+
27
+ export const config = {
28
+ matcher: ['/dashboard/:path*', '/admin/:path*'],
29
+ };
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Server Actions
35
+
36
+ ### 정의 및 사용
37
+ - `'use server'` 지시문으로 서버 액션을 정의한다
38
+ - form의 `action` 속성이나 이벤트 핸들러에서 호출한다
39
+
40
+ ```typescript
41
+ // actions/user.ts
42
+ 'use server';
43
+
44
+ import { revalidatePath } from 'next/cache';
45
+
46
+ export async function createUser(formData: FormData) {
47
+ const name = formData.get('name') as string;
48
+ const email = formData.get('email') as string;
49
+
50
+ await db.user.create({ data: { name, email } });
51
+ revalidatePath('/users');
52
+ }
53
+
54
+ export async function deleteUser(userId: string) {
55
+ await db.user.delete({ where: { id: userId } });
56
+ revalidatePath('/users');
57
+ }
58
+ ```
59
+
60
+ ### Form에서 사용
61
+ ```typescript
62
+ // components/CreateUserForm.tsx
63
+ import { createUser } from '@/actions/user';
64
+
65
+ export function CreateUserForm() {
66
+ return (
67
+ <form action={createUser}>
68
+ <input name="name" type="text" required />
69
+ <input name="email" type="email" required />
70
+ <button type="submit">생성</button>
71
+ </form>
72
+ );
73
+ }
74
+ ```