@kood/claude-code 0.5.3 → 0.5.5
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.
- package/dist/index.js +552 -340
- package/package.json +1 -1
- package/templates/.claude/agents/document-writer.md +73 -306
- package/templates/.claude/instructions/agent-patterns/index.md +7 -7
- package/templates/.claude/instructions/document-templates/ralph-templates.md +71 -0
- package/templates/.claude/instructions/index.md +14 -14
- package/templates/.claude/instructions/multi-agent/agent-roster.md +14 -14
- package/templates/.claude/instructions/multi-agent/index.md +4 -4
- package/templates/.claude/skills/docs-creator/AGENTS.md +54 -176
- package/templates/.claude/skills/docs-creator/SKILL.md +98 -464
- package/templates/.claude/skills/docs-refactor/AGENTS.md +61 -190
- package/templates/.claude/skills/docs-refactor/SKILL.md +67 -443
- package/templates/.claude/skills/execute/SKILL.md +540 -13
- package/templates/.claude/skills/plan/SKILL.md +84 -18
- package/templates/.claude/skills/ralph/SKILL.md +17 -14
- package/templates/.claude/skills/refactor/AGENTS.md +269 -0
- package/templates/.claude/skills/refactor/SKILL.md +424 -66
- package/templates/.claude/skills/stitch-design/README.md +34 -0
- package/templates/.claude/skills/stitch-design/SKILL.md +213 -0
- package/templates/.claude/skills/stitch-design/examples/DESIGN.md +154 -0
- package/templates/.claude/skills/stitch-loop/README.md +54 -0
- package/templates/.claude/skills/stitch-loop/SKILL.md +316 -0
- package/templates/.claude/skills/stitch-loop/examples/SITE.md +73 -0
- package/templates/.claude/skills/stitch-loop/examples/next-prompt.md +25 -0
- package/templates/.claude/skills/stitch-loop/resources/baton-schema.md +61 -0
- package/templates/.claude/skills/stitch-loop/resources/site-template.md +104 -0
- package/templates/.claude/skills/stitch-react/README.md +36 -0
- package/templates/.claude/skills/stitch-react/SKILL.md +323 -0
- package/templates/.claude/skills/stitch-react/examples/gold-standard-card.tsx +88 -0
- package/templates/.claude/skills/stitch-react/package-lock.json +231 -0
- package/templates/.claude/skills/stitch-react/package.json +16 -0
- package/templates/.claude/skills/stitch-react/resources/architecture-checklist.md +15 -0
- package/templates/.claude/skills/stitch-react/resources/component-template.tsx +37 -0
- package/templates/.claude/skills/stitch-react/resources/stitch-api-reference.md +14 -0
- package/templates/.claude/skills/stitch-react/resources/style-guide.json +24 -0
- package/templates/.claude/skills/stitch-react/scripts/fetch-stitch.sh +30 -0
- package/templates/.claude/skills/stitch-react/scripts/validate.js +77 -0
- package/templates/hono/CLAUDE.md +28 -28
- package/templates/hono/docs/architecture.md +24 -24
- package/templates/hono/docs/deployment/cloudflare.md +18 -18
- package/templates/hono/docs/deployment/docker.md +13 -13
- package/templates/hono/docs/deployment/index.md +19 -19
- package/templates/hono/docs/deployment/railway.md +32 -32
- package/templates/hono/docs/deployment/vercel.md +29 -29
- package/templates/hono/docs/guides/conventions.md +57 -57
- package/templates/hono/docs/guides/env-setup.md +47 -47
- package/templates/hono/docs/guides/getting-started.md +27 -27
- package/templates/hono/docs/library/hono/error-handling.md +11 -11
- package/templates/hono/docs/library/hono/index.md +4 -4
- package/templates/hono/docs/library/hono/middleware.md +18 -18
- package/templates/hono/docs/library/hono/rpc.md +7 -7
- package/templates/hono/docs/library/hono/validation.md +6 -6
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +29 -29
- package/templates/hono/docs/library/prisma/config.md +16 -16
- package/templates/hono/docs/library/prisma/index.md +32 -32
- package/templates/hono/docs/library/t3-env/index.md +22 -22
- package/templates/hono/docs/library/zod/index.md +31 -31
- package/templates/nextjs/CLAUDE.md +54 -54
- package/templates/nextjs/docs/architecture.md +146 -146
- package/templates/nextjs/docs/design.md +183 -183
- package/templates/nextjs/docs/guides/conventions.md +86 -86
- package/templates/nextjs/docs/guides/getting-started.md +28 -28
- package/templates/nextjs/docs/guides/routes.md +32 -32
- package/templates/nextjs/docs/library/better-auth/index.md +70 -70
- package/templates/nextjs/docs/library/nextjs/app-router.md +43 -43
- package/templates/nextjs/docs/library/nextjs/caching.md +73 -73
- package/templates/nextjs/docs/library/nextjs/index.md +51 -51
- package/templates/nextjs/docs/library/nextjs/middleware.md +41 -41
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +31 -31
- package/templates/nextjs/docs/library/nextjs/server-actions.md +34 -34
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +20 -20
- package/templates/nextjs/docs/library/prisma/config.md +18 -18
- package/templates/nextjs/docs/library/prisma/crud.md +17 -17
- package/templates/nextjs/docs/library/prisma/index.md +18 -18
- package/templates/nextjs/docs/library/prisma/relations.md +16 -16
- package/templates/nextjs/docs/library/prisma/schema.md +23 -23
- package/templates/nextjs/docs/library/prisma/setup.md +6 -6
- package/templates/nextjs/docs/library/prisma/transactions.md +10 -10
- package/templates/nextjs/docs/library/tanstack-query/index.md +6 -6
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +20 -20
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +15 -15
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +22 -22
- package/templates/nextjs/docs/library/zod/complex-types.md +11 -11
- package/templates/nextjs/docs/library/zod/index.md +8 -8
- package/templates/nextjs/docs/library/zod/transforms.md +11 -11
- package/templates/nextjs/docs/library/zod/validation.md +9 -9
- package/templates/npx/CLAUDE.md +38 -38
- package/templates/npx/docs/library/commander/index.md +12 -12
- package/templates/npx/docs/library/fs-extra/index.md +9 -9
- package/templates/npx/docs/library/prompts/index.md +3 -3
- package/templates/npx/docs/references/patterns.md +12 -12
- package/templates/tanstack-start/CLAUDE.md +54 -54
- package/templates/tanstack-start/docs/architecture.md +128 -128
- package/templates/tanstack-start/docs/design.md +169 -169
- package/templates/tanstack-start/docs/guides/conventions.md +43 -43
- package/templates/tanstack-start/docs/guides/env-setup.md +35 -35
- package/templates/tanstack-start/docs/guides/getting-started.md +19 -19
- package/templates/tanstack-start/docs/guides/hooks.md +45 -45
- package/templates/tanstack-start/docs/guides/routes.md +54 -54
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +19 -19
- package/templates/tanstack-start/docs/library/prisma/config.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/crud.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/relations.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/schema.md +23 -23
- package/templates/tanstack-start/docs/library/prisma/setup.md +6 -6
- package/templates/tanstack-start/docs/library/prisma/transactions.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +19 -19
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +14 -14
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +21 -21
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +11 -11
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +17 -17
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +5 -5
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +8 -8
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +4 -4
- package/templates/tanstack-start/docs/library/zod/complex-types.md +11 -11
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Next.js - Index
|
|
2
2
|
|
|
3
|
-
> Next.js 15 App Router
|
|
3
|
+
> Next.js 15 App Router 핵심 개념
|
|
4
4
|
|
|
5
5
|
<context>
|
|
6
6
|
@app-router.md
|
|
@@ -12,21 +12,21 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 핵심 개념
|
|
16
16
|
|
|
17
|
-
|
|
|
18
|
-
|
|
19
|
-
| **App Router** |
|
|
20
|
-
| **Server Components** |
|
|
21
|
-
| **Client Components** |
|
|
22
|
-
| **Server Actions** | `"use server"`
|
|
23
|
-
| **Route Handlers** | REST API
|
|
17
|
+
| 개념 | 설명 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| **App Router** | 파일 기반 라우팅 (`app/` 디렉토리) |
|
|
20
|
+
| **Server Components** | 기본 컴포넌트 (서버 렌더링) |
|
|
21
|
+
| **Client Components** | `"use client"` 선언 필요 |
|
|
22
|
+
| **Server Actions** | `"use server"` 함수 (타입 안전 API) |
|
|
23
|
+
| **Route Handlers** | REST API 엔드포인트 (`app/api/`) |
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## 빠른 시작
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### 프로젝트 생성
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npx create-next-app@latest my-app --typescript --tailwind --app
|
|
@@ -34,10 +34,10 @@ cd my-app
|
|
|
34
34
|
npm run dev
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### 기본 페이지
|
|
38
38
|
|
|
39
39
|
```typescript
|
|
40
|
-
// app/page.tsx (Server Component -
|
|
40
|
+
// app/page.tsx (Server Component - 기본)
|
|
41
41
|
export default async function HomePage() {
|
|
42
42
|
const data = await fetch('https://api.example.com/data')
|
|
43
43
|
const json = await data.json()
|
|
@@ -62,11 +62,11 @@ export function Counter() {
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## 파일 구조
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
app/
|
|
69
|
-
├── layout.tsx # Root layout (
|
|
69
|
+
├── layout.tsx # Root layout (필수)
|
|
70
70
|
├── page.tsx # Home (/)
|
|
71
71
|
├── about/
|
|
72
72
|
│ └── page.tsx # /about
|
|
@@ -81,33 +81,33 @@ app/
|
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
|
-
##
|
|
84
|
+
## 주요 파일
|
|
85
85
|
|
|
86
|
-
|
|
|
87
|
-
|
|
88
|
-
| `layout.tsx` |
|
|
89
|
-
| `page.tsx` |
|
|
90
|
-
| `loading.tsx` |
|
|
91
|
-
| `error.tsx` |
|
|
92
|
-
| `not-found.tsx` | 404
|
|
93
|
-
| `route.ts` | API
|
|
86
|
+
| 파일 | 용도 |
|
|
87
|
+
|------|------|
|
|
88
|
+
| `layout.tsx` | 공통 레이아웃 (중첩 가능) |
|
|
89
|
+
| `page.tsx` | 페이지 컴포넌트 |
|
|
90
|
+
| `loading.tsx` | 로딩 UI (Suspense) |
|
|
91
|
+
| `error.tsx` | 에러 UI (Error Boundary) |
|
|
92
|
+
| `not-found.tsx` | 404 페이지 |
|
|
93
|
+
| `route.ts` | API 엔드포인트 |
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
|
97
97
|
## Server vs Client Components
|
|
98
98
|
|
|
99
|
-
|
|
|
100
|
-
|
|
101
|
-
|
|
|
102
|
-
|
|
|
99
|
+
| 구분 | Server | Client |
|
|
100
|
+
|------|--------|--------|
|
|
101
|
+
| 선언 | 기본 | `"use client"` |
|
|
102
|
+
| 데이터 페칭 | ✅ async/await | ❌ (useQuery 사용) |
|
|
103
103
|
| Hooks | ❌ | ✅ useState, useEffect |
|
|
104
|
-
|
|
|
104
|
+
| 브라우저 API | ❌ | ✅ window, localStorage |
|
|
105
105
|
| Event Handlers | ❌ | ✅ onClick, onChange |
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
- Server
|
|
109
|
-
- Server
|
|
110
|
-
- Client
|
|
107
|
+
**규칙:**
|
|
108
|
+
- Server Component가 기본 → Client Component 필요 시만 `"use client"` 추가
|
|
109
|
+
- Server Component 안에 Client Component 포함 가능
|
|
110
|
+
- Client Component 안에 Server Component 불가 (props로 전달은 가능)
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
@@ -183,13 +183,13 @@ export const config = {
|
|
|
183
183
|
|
|
184
184
|
---
|
|
185
185
|
|
|
186
|
-
##
|
|
186
|
+
## 데이터 페칭
|
|
187
187
|
|
|
188
|
-
### Server Component (
|
|
188
|
+
### Server Component (권장)
|
|
189
189
|
|
|
190
190
|
```typescript
|
|
191
191
|
export default async function PostsPage() {
|
|
192
|
-
const posts = await prisma.post.findMany() //
|
|
192
|
+
const posts = await prisma.post.findMany() // 직접 DB 접근
|
|
193
193
|
return <PostsList posts={posts} />
|
|
194
194
|
}
|
|
195
195
|
```
|
|
@@ -213,13 +213,13 @@ export function PostsList() {
|
|
|
213
213
|
|
|
214
214
|
---
|
|
215
215
|
|
|
216
|
-
##
|
|
216
|
+
## 캐싱
|
|
217
217
|
|
|
218
|
-
|
|
|
219
|
-
|
|
220
|
-
| `revalidatePath("/posts")` |
|
|
221
|
-
| `revalidateTag("posts")` |
|
|
222
|
-
| `unstable_cache()` |
|
|
218
|
+
| 함수 | 용도 |
|
|
219
|
+
|------|------|
|
|
220
|
+
| `revalidatePath("/posts")` | 특정 경로 캐시 무효화 |
|
|
221
|
+
| `revalidateTag("posts")` | 태그 기반 캐시 무효화 |
|
|
222
|
+
| `unstable_cache()` | 함수 결과 캐싱 |
|
|
223
223
|
|
|
224
224
|
```typescript
|
|
225
225
|
import { revalidatePath, revalidateTag } from "next/cache"
|
|
@@ -227,8 +227,8 @@ import { revalidatePath, revalidateTag } from "next/cache"
|
|
|
227
227
|
export async function createPost(data: PostInput) {
|
|
228
228
|
const post = await prisma.post.create({ data })
|
|
229
229
|
|
|
230
|
-
revalidatePath("/posts") //
|
|
231
|
-
revalidateTag("posts") //
|
|
230
|
+
revalidatePath("/posts") // /posts 캐시 무효화
|
|
231
|
+
revalidateTag("posts") // "posts" 태그 캐시 무효화
|
|
232
232
|
|
|
233
233
|
return post
|
|
234
234
|
}
|
|
@@ -236,7 +236,7 @@ export async function createPost(data: PostInput) {
|
|
|
236
236
|
|
|
237
237
|
---
|
|
238
238
|
|
|
239
|
-
##
|
|
239
|
+
## 환경 변수
|
|
240
240
|
|
|
241
241
|
```bash
|
|
242
242
|
# .env.local
|
|
@@ -246,15 +246,15 @@ NEXTAUTH_URL="http://localhost:3000"
|
|
|
246
246
|
NEXT_PUBLIC_API_URL="https://api.example.com"
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
- `NEXT_PUBLIC_*`:
|
|
251
|
-
-
|
|
249
|
+
**규칙:**
|
|
250
|
+
- `NEXT_PUBLIC_*`: 클라이언트에서 접근 가능
|
|
251
|
+
- 나머지: 서버 전용
|
|
252
252
|
|
|
253
253
|
---
|
|
254
254
|
|
|
255
|
-
##
|
|
255
|
+
## 배포
|
|
256
256
|
|
|
257
|
-
### Vercel (
|
|
257
|
+
### Vercel (권장)
|
|
258
258
|
|
|
259
259
|
```bash
|
|
260
260
|
npm i -g vercel
|
|
@@ -282,7 +282,7 @@ npm start
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
-
##
|
|
285
|
+
## 참조
|
|
286
286
|
|
|
287
287
|
- [App Router](app-router.md)
|
|
288
288
|
- [Server Actions](server-actions.md)
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# Middleware
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 요청 처리 전 실행되는 함수
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 기본 사용법
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
// middleware.ts (
|
|
10
|
+
// middleware.ts (루트)
|
|
11
11
|
import { NextResponse } from "next/server"
|
|
12
12
|
import type { NextRequest } from "next/server"
|
|
13
13
|
|
|
14
14
|
export function middleware(request: NextRequest) {
|
|
15
|
-
//
|
|
15
|
+
// 로직 실행...
|
|
16
16
|
return NextResponse.next()
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// 매처 설정
|
|
20
20
|
export const config = {
|
|
21
21
|
matcher: ["/dashboard/:path*", "/api/:path*"],
|
|
22
22
|
}
|
|
@@ -24,12 +24,12 @@ export const config = {
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## Response
|
|
27
|
+
## Response 타입
|
|
28
28
|
|
|
29
29
|
### NextResponse.next()
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
|
-
//
|
|
32
|
+
// 요청을 다음 미들웨어 또는 라우트로 전달
|
|
33
33
|
export function middleware(request: NextRequest) {
|
|
34
34
|
return NextResponse.next()
|
|
35
35
|
}
|
|
@@ -38,7 +38,7 @@ export function middleware(request: NextRequest) {
|
|
|
38
38
|
### NextResponse.redirect()
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
|
-
//
|
|
41
|
+
// 다른 URL로 리다이렉트
|
|
42
42
|
export function middleware(request: NextRequest) {
|
|
43
43
|
return NextResponse.redirect(new URL("/login", request.url))
|
|
44
44
|
}
|
|
@@ -47,7 +47,7 @@ export function middleware(request: NextRequest) {
|
|
|
47
47
|
### NextResponse.rewrite()
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
|
-
//
|
|
50
|
+
// URL은 유지하되 다른 페이지 렌더링
|
|
51
51
|
export function middleware(request: NextRequest) {
|
|
52
52
|
return NextResponse.rewrite(new URL("/dashboard/home", request.url))
|
|
53
53
|
}
|
|
@@ -55,7 +55,7 @@ export function middleware(request: NextRequest) {
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## 인증
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
61
|
import { NextResponse } from "next/server"
|
|
@@ -64,7 +64,7 @@ import type { NextRequest } from "next/server"
|
|
|
64
64
|
export function middleware(request: NextRequest) {
|
|
65
65
|
const token = request.cookies.get("token")
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// 토큰 없으면 로그인 페이지로
|
|
68
68
|
if (!token) {
|
|
69
69
|
return NextResponse.redirect(new URL("/login", request.url))
|
|
70
70
|
}
|
|
@@ -79,9 +79,9 @@ export const config = {
|
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
82
|
-
##
|
|
82
|
+
## 쿠키 처리
|
|
83
83
|
|
|
84
|
-
###
|
|
84
|
+
### 읽기
|
|
85
85
|
|
|
86
86
|
```typescript
|
|
87
87
|
export function middleware(request: NextRequest) {
|
|
@@ -94,7 +94,7 @@ export function middleware(request: NextRequest) {
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
###
|
|
97
|
+
### 설정
|
|
98
98
|
|
|
99
99
|
```typescript
|
|
100
100
|
export function middleware(request: NextRequest) {
|
|
@@ -103,7 +103,7 @@ export function middleware(request: NextRequest) {
|
|
|
103
103
|
response.cookies.set("visited", "true", {
|
|
104
104
|
httpOnly: true,
|
|
105
105
|
secure: true,
|
|
106
|
-
maxAge: 60 * 60 * 24 * 7, // 7
|
|
106
|
+
maxAge: 60 * 60 * 24 * 7, // 7일
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
return response
|
|
@@ -112,9 +112,9 @@ export function middleware(request: NextRequest) {
|
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
115
|
-
## Headers
|
|
115
|
+
## Headers 처리
|
|
116
116
|
|
|
117
|
-
###
|
|
117
|
+
### 읽기
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
120
|
export function middleware(request: NextRequest) {
|
|
@@ -127,7 +127,7 @@ export function middleware(request: NextRequest) {
|
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
###
|
|
130
|
+
### 설정
|
|
131
131
|
|
|
132
132
|
```typescript
|
|
133
133
|
export function middleware(request: NextRequest) {
|
|
@@ -142,13 +142,13 @@ export function middleware(request: NextRequest) {
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
-
##
|
|
145
|
+
## 경로별 처리
|
|
146
146
|
|
|
147
147
|
```typescript
|
|
148
148
|
export function middleware(request: NextRequest) {
|
|
149
149
|
const { pathname } = request.nextUrl
|
|
150
150
|
|
|
151
|
-
// /api/*
|
|
151
|
+
// /api/* 경로
|
|
152
152
|
if (pathname.startsWith("/api/")) {
|
|
153
153
|
const token = request.headers.get("authorization")
|
|
154
154
|
|
|
@@ -157,7 +157,7 @@ export function middleware(request: NextRequest) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// /admin/*
|
|
160
|
+
// /admin/* 경로
|
|
161
161
|
if (pathname.startsWith("/admin/")) {
|
|
162
162
|
const role = request.cookies.get("role")?.value
|
|
163
163
|
|
|
@@ -172,9 +172,9 @@ export function middleware(request: NextRequest) {
|
|
|
172
172
|
|
|
173
173
|
---
|
|
174
174
|
|
|
175
|
-
## Matcher
|
|
175
|
+
## Matcher 설정
|
|
176
176
|
|
|
177
|
-
###
|
|
177
|
+
### 배열
|
|
178
178
|
|
|
179
179
|
```typescript
|
|
180
180
|
export const config = {
|
|
@@ -182,23 +182,23 @@ export const config = {
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
###
|
|
185
|
+
### 정규식
|
|
186
186
|
|
|
187
187
|
```typescript
|
|
188
188
|
export const config = {
|
|
189
189
|
matcher: [
|
|
190
190
|
/*
|
|
191
|
-
*
|
|
192
|
-
* - _next/static (
|
|
193
|
-
* - _next/image (
|
|
194
|
-
* - favicon.ico (
|
|
191
|
+
* 다음 경로 제외:
|
|
192
|
+
* - _next/static (정적 파일)
|
|
193
|
+
* - _next/image (이미지 최적화)
|
|
194
|
+
* - favicon.ico (파비콘)
|
|
195
195
|
*/
|
|
196
196
|
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
197
197
|
],
|
|
198
198
|
}
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
###
|
|
201
|
+
### 조건부
|
|
202
202
|
|
|
203
203
|
```typescript
|
|
204
204
|
export const config = {
|
|
@@ -216,7 +216,7 @@ export const config = {
|
|
|
216
216
|
|
|
217
217
|
---
|
|
218
218
|
|
|
219
|
-
##
|
|
219
|
+
## 로깅
|
|
220
220
|
|
|
221
221
|
```typescript
|
|
222
222
|
export function middleware(request: NextRequest) {
|
|
@@ -248,7 +248,7 @@ import type { NextRequest } from "next/server"
|
|
|
248
248
|
const rateLimit = new Map<string, { count: number; resetAt: number }>()
|
|
249
249
|
|
|
250
250
|
const LIMIT = 10 // 10 requests
|
|
251
|
-
const WINDOW = 60 * 1000 // 1
|
|
251
|
+
const WINDOW = 60 * 1000 // 1분
|
|
252
252
|
|
|
253
253
|
export function middleware(request: NextRequest) {
|
|
254
254
|
const ip = request.ip || "unknown"
|
|
@@ -296,7 +296,7 @@ export function middleware(request: NextRequest) {
|
|
|
296
296
|
|
|
297
297
|
---
|
|
298
298
|
|
|
299
|
-
## A/B
|
|
299
|
+
## A/B 테스팅
|
|
300
300
|
|
|
301
301
|
```typescript
|
|
302
302
|
import { NextResponse } from "next/server"
|
|
@@ -332,12 +332,12 @@ export const config = {
|
|
|
332
332
|
|
|
333
333
|
---
|
|
334
334
|
|
|
335
|
-
##
|
|
335
|
+
## 베스트 프랙티스
|
|
336
336
|
|
|
337
337
|
### ✅ DO
|
|
338
338
|
|
|
339
339
|
```typescript
|
|
340
|
-
// 1.
|
|
340
|
+
// 1. 가벼운 로직만
|
|
341
341
|
export function middleware(request: NextRequest) {
|
|
342
342
|
const token = request.cookies.get("token")
|
|
343
343
|
|
|
@@ -348,7 +348,7 @@ export function middleware(request: NextRequest) {
|
|
|
348
348
|
return NextResponse.next()
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
// 2.
|
|
351
|
+
// 2. matcher 설정
|
|
352
352
|
export const config = {
|
|
353
353
|
matcher: ["/dashboard/:path*"],
|
|
354
354
|
}
|
|
@@ -357,23 +357,23 @@ export const config = {
|
|
|
357
357
|
### ❌ DON'T
|
|
358
358
|
|
|
359
359
|
```typescript
|
|
360
|
-
// 1.
|
|
360
|
+
// 1. 무거운 DB 쿼리
|
|
361
361
|
export async function middleware(request: NextRequest) {
|
|
362
|
-
// ❌
|
|
362
|
+
// ❌ 미들웨어에서 DB 쿼리 금지
|
|
363
363
|
const user = await prisma.user.findUnique({ where: { id: "..." } })
|
|
364
364
|
return NextResponse.next()
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
// 2.
|
|
367
|
+
// 2. matcher 없이 모든 요청 처리
|
|
368
368
|
export function middleware(request: NextRequest) {
|
|
369
|
-
// ❌
|
|
369
|
+
// ❌ 성능 저하
|
|
370
370
|
return NextResponse.next()
|
|
371
371
|
}
|
|
372
372
|
```
|
|
373
373
|
|
|
374
374
|
---
|
|
375
375
|
|
|
376
|
-
##
|
|
376
|
+
## NextAuth.js와 함께 사용
|
|
377
377
|
|
|
378
378
|
```typescript
|
|
379
379
|
// middleware.ts
|
|
@@ -386,6 +386,6 @@ export const config = {
|
|
|
386
386
|
|
|
387
387
|
---
|
|
388
388
|
|
|
389
|
-
##
|
|
389
|
+
## 참조
|
|
390
390
|
|
|
391
391
|
- [Next.js Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Route Handlers
|
|
2
2
|
|
|
3
|
-
> REST API
|
|
3
|
+
> REST API 엔드포인트 (`app/api/`)
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 기본 사용법
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
10
|
// app/api/posts/route.ts
|
|
@@ -24,7 +24,7 @@ export async function POST(request: NextRequest) {
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## HTTP
|
|
27
|
+
## HTTP 메서드
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
30
|
// app/api/posts/route.ts
|
|
@@ -39,7 +39,7 @@ export async function OPTIONS(request: NextRequest) {}
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## 동적 라우트
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
// app/api/posts/[id]/route.ts
|
|
@@ -67,7 +67,7 @@ export async function DELETE(
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
## Request
|
|
70
|
+
## Request 처리
|
|
71
71
|
|
|
72
72
|
### Query Parameters
|
|
73
73
|
|
|
@@ -112,7 +112,7 @@ export async function POST(request: NextRequest) {
|
|
|
112
112
|
const title = formData.get("title") as string
|
|
113
113
|
const file = formData.get("file") as File
|
|
114
114
|
|
|
115
|
-
//
|
|
115
|
+
// 파일 처리...
|
|
116
116
|
|
|
117
117
|
return NextResponse.json({ success: true })
|
|
118
118
|
}
|
|
@@ -125,7 +125,7 @@ export async function GET(request: NextRequest) {
|
|
|
125
125
|
const token = request.headers.get("authorization")
|
|
126
126
|
const userAgent = request.headers.get("user-agent")
|
|
127
127
|
|
|
128
|
-
//
|
|
128
|
+
// 헤더 사용...
|
|
129
129
|
|
|
130
130
|
return NextResponse.json({ data: "..." })
|
|
131
131
|
}
|
|
@@ -133,7 +133,7 @@ export async function GET(request: NextRequest) {
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
-
## Response
|
|
136
|
+
## Response 처리
|
|
137
137
|
|
|
138
138
|
### JSON Response
|
|
139
139
|
|
|
@@ -178,14 +178,14 @@ import { cookies } from "next/headers"
|
|
|
178
178
|
export async function GET() {
|
|
179
179
|
const cookieStore = cookies()
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// 쿠키 읽기
|
|
182
182
|
const token = cookieStore.get("token")
|
|
183
183
|
|
|
184
|
-
//
|
|
184
|
+
// 쿠키 설정
|
|
185
185
|
cookieStore.set("token", "value", {
|
|
186
186
|
httpOnly: true,
|
|
187
187
|
secure: true,
|
|
188
|
-
maxAge: 60 * 60 * 24 * 7, // 7
|
|
188
|
+
maxAge: 60 * 60 * 24 * 7, // 7일
|
|
189
189
|
})
|
|
190
190
|
|
|
191
191
|
return NextResponse.json({ success: true })
|
|
@@ -194,7 +194,7 @@ export async function GET() {
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
-
## Zod
|
|
197
|
+
## Zod 검증
|
|
198
198
|
|
|
199
199
|
```typescript
|
|
200
200
|
import { z } from "zod"
|
|
@@ -229,7 +229,7 @@ export async function POST(request: NextRequest) {
|
|
|
229
229
|
|
|
230
230
|
---
|
|
231
231
|
|
|
232
|
-
##
|
|
232
|
+
## 인증
|
|
233
233
|
|
|
234
234
|
```typescript
|
|
235
235
|
import { auth } from "@/lib/auth"
|
|
@@ -286,7 +286,7 @@ export async function OPTIONS() {
|
|
|
286
286
|
|
|
287
287
|
---
|
|
288
288
|
|
|
289
|
-
##
|
|
289
|
+
## 스트리밍
|
|
290
290
|
|
|
291
291
|
```typescript
|
|
292
292
|
export async function GET() {
|
|
@@ -314,7 +314,7 @@ export async function GET() {
|
|
|
314
314
|
|
|
315
315
|
---
|
|
316
316
|
|
|
317
|
-
##
|
|
317
|
+
## 에러 처리
|
|
318
318
|
|
|
319
319
|
```typescript
|
|
320
320
|
export async function GET(
|
|
@@ -341,17 +341,17 @@ export async function GET(
|
|
|
341
341
|
|
|
342
342
|
---
|
|
343
343
|
|
|
344
|
-
##
|
|
344
|
+
## 캐싱
|
|
345
345
|
|
|
346
346
|
```typescript
|
|
347
|
-
//
|
|
347
|
+
// 정적 응답 (빌드 시 생성)
|
|
348
348
|
export const dynamic = "force-static"
|
|
349
349
|
|
|
350
|
-
//
|
|
350
|
+
// 동적 응답 (매 요청마다)
|
|
351
351
|
export const dynamic = "force-dynamic"
|
|
352
352
|
|
|
353
|
-
// Revalidate (
|
|
354
|
-
export const revalidate = 60 // 60
|
|
353
|
+
// Revalidate (n초마다 재생성)
|
|
354
|
+
export const revalidate = 60 // 60초
|
|
355
355
|
|
|
356
356
|
export async function GET() {
|
|
357
357
|
const posts = await prisma.post.findMany()
|
|
@@ -363,20 +363,20 @@ export async function GET() {
|
|
|
363
363
|
|
|
364
364
|
## Server Actions vs Route Handlers
|
|
365
365
|
|
|
366
|
-
|
|
|
367
|
-
|
|
368
|
-
|
|
|
369
|
-
|
|
|
370
|
-
|
|
|
371
|
-
|
|
|
372
|
-
|
|
|
366
|
+
| 기준 | Server Actions | Route Handlers |
|
|
367
|
+
|------|----------------|----------------|
|
|
368
|
+
| **용도** | 폼 제출, 뮤테이션 | REST API, 외부 연동 |
|
|
369
|
+
| **타입** | ✅ 자동 타입 추론 | ❌ 수동 타입 정의 |
|
|
370
|
+
| **캐싱** | revalidatePath | export const revalidate |
|
|
371
|
+
| **인증** | await auth() | await auth() |
|
|
372
|
+
| **권장** | 내부 API | 외부 API, 웹훅 |
|
|
373
373
|
|
|
374
|
-
|
|
375
|
-
-
|
|
376
|
-
-
|
|
374
|
+
**권장 사항:**
|
|
375
|
+
- 내부 API → Server Actions
|
|
376
|
+
- 외부 API, 웹훅, 써드파티 연동 → Route Handlers
|
|
377
377
|
|
|
378
378
|
---
|
|
379
379
|
|
|
380
|
-
##
|
|
380
|
+
## 참조
|
|
381
381
|
|
|
382
382
|
- [Next.js Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
|