@kood/claude-code 0.3.5 → 0.3.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.
Files changed (35) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/nextjs/CLAUDE.md +228 -0
  4. package/templates/nextjs/docs/design.md +558 -0
  5. package/templates/nextjs/docs/guides/conventions.md +343 -0
  6. package/templates/nextjs/docs/guides/getting-started.md +367 -0
  7. package/templates/nextjs/docs/guides/routes.md +342 -0
  8. package/templates/nextjs/docs/library/better-auth/index.md +541 -0
  9. package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
  10. package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
  11. package/templates/nextjs/docs/library/nextjs/index.md +291 -0
  12. package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
  13. package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
  14. package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
  15. package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
  16. package/templates/nextjs/docs/library/prisma/config.md +77 -0
  17. package/templates/nextjs/docs/library/prisma/crud.md +90 -0
  18. package/templates/nextjs/docs/library/prisma/index.md +73 -0
  19. package/templates/nextjs/docs/library/prisma/relations.md +69 -0
  20. package/templates/nextjs/docs/library/prisma/schema.md +98 -0
  21. package/templates/nextjs/docs/library/prisma/setup.md +49 -0
  22. package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
  23. package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
  24. package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
  25. package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
  26. package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
  27. package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
  28. package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
  29. package/templates/nextjs/docs/library/zod/index.md +56 -0
  30. package/templates/nextjs/docs/library/zod/transforms.md +51 -0
  31. package/templates/nextjs/docs/library/zod/validation.md +70 -0
  32. package/templates/tanstack-start/CLAUDE.md +7 -3
  33. package/templates/tanstack-start/docs/architecture.md +38 -7
  34. package/templates/tanstack-start/docs/guides/hooks.md +28 -0
  35. package/templates/tanstack-start/docs/guides/routes.md +29 -10
@@ -0,0 +1,291 @@
1
+ # Next.js - Index
2
+
3
+ > Next.js 15 App Router 핵심 개념
4
+
5
+ <context>
6
+ @app-router.md
7
+ @server-actions.md
8
+ @route-handlers.md
9
+ @middleware.md
10
+ @caching.md
11
+ </context>
12
+
13
+ ---
14
+
15
+ ## 핵심 개념
16
+
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
+
25
+ ---
26
+
27
+ ## 빠른 시작
28
+
29
+ ### 프로젝트 생성
30
+
31
+ ```bash
32
+ npx create-next-app@latest my-app --typescript --tailwind --app
33
+ cd my-app
34
+ npm run dev
35
+ ```
36
+
37
+ ### 기본 페이지
38
+
39
+ ```typescript
40
+ // app/page.tsx (Server Component - 기본)
41
+ export default async function HomePage() {
42
+ const data = await fetch('https://api.example.com/data')
43
+ const json = await data.json()
44
+
45
+ return <div>{json.title}</div>
46
+ }
47
+ ```
48
+
49
+ ### Client Component
50
+
51
+ ```typescript
52
+ // app/_components/counter.tsx
53
+ "use client"
54
+
55
+ import { useState } from "react"
56
+
57
+ export function Counter() {
58
+ const [count, setCount] = useState(0)
59
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 파일 구조
66
+
67
+ ```
68
+ app/
69
+ ├── layout.tsx # Root layout (필수)
70
+ ├── page.tsx # Home (/)
71
+ ├── about/
72
+ │ └── page.tsx # /about
73
+ ├── blog/
74
+ │ ├── page.tsx # /blog
75
+ │ └── [slug]/
76
+ │ └── page.tsx # /blog/:slug
77
+ └── api/
78
+ └── posts/
79
+ └── route.ts # API /api/posts
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 주요 파일
85
+
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
+
95
+ ---
96
+
97
+ ## Server vs Client Components
98
+
99
+ | 구분 | Server | Client |
100
+ |------|--------|--------|
101
+ | 선언 | 기본 | `"use client"` |
102
+ | 데이터 페칭 | ✅ async/await | ❌ (useQuery 사용) |
103
+ | Hooks | ❌ | ✅ useState, useEffect |
104
+ | 브라우저 API | ❌ | ✅ window, localStorage |
105
+ | Event Handlers | ❌ | ✅ onClick, onChange |
106
+
107
+ **규칙:**
108
+ - Server Component가 기본 → Client Component 필요 시만 `"use client"` 추가
109
+ - Server Component 안에 Client Component 포함 가능
110
+ - Client Component 안에 Server Component 불가 (props로 전달은 가능)
111
+
112
+ ---
113
+
114
+ ## Server Actions
115
+
116
+ ```typescript
117
+ // app/actions.ts
118
+ "use server"
119
+
120
+ import { z } from "zod"
121
+ import { revalidatePath } from "next/cache"
122
+
123
+ const schema = z.object({
124
+ title: z.string().min(1),
125
+ content: z.string(),
126
+ })
127
+
128
+ export async function createPost(formData: FormData) {
129
+ const parsed = schema.parse({
130
+ title: formData.get("title"),
131
+ content: formData.get("content"),
132
+ })
133
+
134
+ const post = await prisma.post.create({ data: parsed })
135
+ revalidatePath("/posts")
136
+ return post
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Route Handlers
143
+
144
+ ```typescript
145
+ // app/api/posts/route.ts
146
+ import { NextRequest, NextResponse } from "next/server"
147
+
148
+ export async function GET(request: NextRequest) {
149
+ const posts = await prisma.post.findMany()
150
+ return NextResponse.json(posts)
151
+ }
152
+
153
+ export async function POST(request: NextRequest) {
154
+ const body = await request.json()
155
+ const post = await prisma.post.create({ data: body })
156
+ return NextResponse.json(post, { status: 201 })
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Middleware
163
+
164
+ ```typescript
165
+ // middleware.ts
166
+ import { NextResponse } from "next/server"
167
+ import type { NextRequest } from "next/server"
168
+
169
+ export function middleware(request: NextRequest) {
170
+ const token = request.cookies.get("token")
171
+
172
+ if (!token) {
173
+ return NextResponse.redirect(new URL("/login", request.url))
174
+ }
175
+
176
+ return NextResponse.next()
177
+ }
178
+
179
+ export const config = {
180
+ matcher: ["/dashboard/:path*", "/profile/:path*"],
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## 데이터 페칭
187
+
188
+ ### Server Component (권장)
189
+
190
+ ```typescript
191
+ export default async function PostsPage() {
192
+ const posts = await prisma.post.findMany() // 직접 DB 접근
193
+ return <PostsList posts={posts} />
194
+ }
195
+ ```
196
+
197
+ ### Client Component (TanStack Query)
198
+
199
+ ```typescript
200
+ "use client"
201
+
202
+ import { useQuery } from "@tanstack/react-query"
203
+
204
+ export function PostsList() {
205
+ const { data } = useQuery({
206
+ queryKey: ["posts"],
207
+ queryFn: () => fetch("/api/posts").then(r => r.json()),
208
+ })
209
+
210
+ return <ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## 캐싱
217
+
218
+ | 함수 | 용도 |
219
+ |------|------|
220
+ | `revalidatePath("/posts")` | 특정 경로 캐시 무효화 |
221
+ | `revalidateTag("posts")` | 태그 기반 캐시 무효화 |
222
+ | `unstable_cache()` | 함수 결과 캐싱 |
223
+
224
+ ```typescript
225
+ import { revalidatePath, revalidateTag } from "next/cache"
226
+
227
+ export async function createPost(data: PostInput) {
228
+ const post = await prisma.post.create({ data })
229
+
230
+ revalidatePath("/posts") // /posts 캐시 무효화
231
+ revalidateTag("posts") // "posts" 태그 캐시 무효화
232
+
233
+ return post
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## 환경 변수
240
+
241
+ ```bash
242
+ # .env.local
243
+ DATABASE_URL="postgresql://..."
244
+ NEXTAUTH_SECRET="..."
245
+ NEXTAUTH_URL="http://localhost:3000"
246
+ NEXT_PUBLIC_API_URL="https://api.example.com"
247
+ ```
248
+
249
+ **규칙:**
250
+ - `NEXT_PUBLIC_*`: 클라이언트에서 접근 가능
251
+ - 나머지: 서버 전용
252
+
253
+ ---
254
+
255
+ ## 배포
256
+
257
+ ### Vercel (권장)
258
+
259
+ ```bash
260
+ npm i -g vercel
261
+ vercel
262
+ ```
263
+
264
+ ### Docker
265
+
266
+ ```dockerfile
267
+ FROM node:20-alpine
268
+ WORKDIR /app
269
+ COPY package*.json ./
270
+ RUN npm ci
271
+ COPY . .
272
+ RUN npm run build
273
+ CMD ["npm", "start"]
274
+ ```
275
+
276
+ ### Node.js
277
+
278
+ ```bash
279
+ npm run build
280
+ npm start
281
+ ```
282
+
283
+ ---
284
+
285
+ ## 참조
286
+
287
+ - [App Router](app-router.md)
288
+ - [Server Actions](server-actions.md)
289
+ - [Route Handlers](route-handlers.md)
290
+ - [Middleware](middleware.md)
291
+ - [Caching](caching.md)
@@ -0,0 +1,391 @@
1
+ # Middleware
2
+
3
+ > 요청 처리 전 실행되는 함수
4
+
5
+ ---
6
+
7
+ ## 기본 사용법
8
+
9
+ ```typescript
10
+ // middleware.ts (루트)
11
+ import { NextResponse } from "next/server"
12
+ import type { NextRequest } from "next/server"
13
+
14
+ export function middleware(request: NextRequest) {
15
+ // 로직 실행...
16
+ return NextResponse.next()
17
+ }
18
+
19
+ // 매처 설정
20
+ export const config = {
21
+ matcher: ["/dashboard/:path*", "/api/:path*"],
22
+ }
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Response 타입
28
+
29
+ ### NextResponse.next()
30
+
31
+ ```typescript
32
+ // 요청을 다음 미들웨어 또는 라우트로 전달
33
+ export function middleware(request: NextRequest) {
34
+ return NextResponse.next()
35
+ }
36
+ ```
37
+
38
+ ### NextResponse.redirect()
39
+
40
+ ```typescript
41
+ // 다른 URL로 리다이렉트
42
+ export function middleware(request: NextRequest) {
43
+ return NextResponse.redirect(new URL("/login", request.url))
44
+ }
45
+ ```
46
+
47
+ ### NextResponse.rewrite()
48
+
49
+ ```typescript
50
+ // URL은 유지하되 다른 페이지 렌더링
51
+ export function middleware(request: NextRequest) {
52
+ return NextResponse.rewrite(new URL("/dashboard/home", request.url))
53
+ }
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 인증
59
+
60
+ ```typescript
61
+ import { NextResponse } from "next/server"
62
+ import type { NextRequest } from "next/server"
63
+
64
+ export function middleware(request: NextRequest) {
65
+ const token = request.cookies.get("token")
66
+
67
+ // 토큰 없으면 로그인 페이지로
68
+ if (!token) {
69
+ return NextResponse.redirect(new URL("/login", request.url))
70
+ }
71
+
72
+ return NextResponse.next()
73
+ }
74
+
75
+ export const config = {
76
+ matcher: ["/dashboard/:path*", "/profile/:path*"],
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 쿠키 처리
83
+
84
+ ### 읽기
85
+
86
+ ```typescript
87
+ export function middleware(request: NextRequest) {
88
+ const token = request.cookies.get("token")
89
+ const userId = request.cookies.get("userId")
90
+
91
+ console.log({ token, userId })
92
+
93
+ return NextResponse.next()
94
+ }
95
+ ```
96
+
97
+ ### 설정
98
+
99
+ ```typescript
100
+ export function middleware(request: NextRequest) {
101
+ const response = NextResponse.next()
102
+
103
+ response.cookies.set("visited", "true", {
104
+ httpOnly: true,
105
+ secure: true,
106
+ maxAge: 60 * 60 * 24 * 7, // 7일
107
+ })
108
+
109
+ return response
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Headers 처리
116
+
117
+ ### 읽기
118
+
119
+ ```typescript
120
+ export function middleware(request: NextRequest) {
121
+ const userAgent = request.headers.get("user-agent")
122
+ const authorization = request.headers.get("authorization")
123
+
124
+ console.log({ userAgent, authorization })
125
+
126
+ return NextResponse.next()
127
+ }
128
+ ```
129
+
130
+ ### 설정
131
+
132
+ ```typescript
133
+ export function middleware(request: NextRequest) {
134
+ const response = NextResponse.next()
135
+
136
+ response.headers.set("X-Custom-Header", "value")
137
+ response.headers.set("X-Request-Id", crypto.randomUUID())
138
+
139
+ return response
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 경로별 처리
146
+
147
+ ```typescript
148
+ export function middleware(request: NextRequest) {
149
+ const { pathname } = request.nextUrl
150
+
151
+ // /api/* 경로
152
+ if (pathname.startsWith("/api/")) {
153
+ const token = request.headers.get("authorization")
154
+
155
+ if (!token) {
156
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
157
+ }
158
+ }
159
+
160
+ // /admin/* 경로
161
+ if (pathname.startsWith("/admin/")) {
162
+ const role = request.cookies.get("role")?.value
163
+
164
+ if (role !== "admin") {
165
+ return NextResponse.redirect(new URL("/", request.url))
166
+ }
167
+ }
168
+
169
+ return NextResponse.next()
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Matcher 설정
176
+
177
+ ### 배열
178
+
179
+ ```typescript
180
+ export const config = {
181
+ matcher: ["/dashboard/:path*", "/profile/:path*"],
182
+ }
183
+ ```
184
+
185
+ ### 정규식
186
+
187
+ ```typescript
188
+ export const config = {
189
+ matcher: [
190
+ /*
191
+ * 다음 경로 제외:
192
+ * - _next/static (정적 파일)
193
+ * - _next/image (이미지 최적화)
194
+ * - favicon.ico (파비콘)
195
+ */
196
+ "/((?!_next/static|_next/image|favicon.ico).*)",
197
+ ],
198
+ }
199
+ ```
200
+
201
+ ### 조건부
202
+
203
+ ```typescript
204
+ export const config = {
205
+ matcher: [
206
+ "/dashboard/:path*",
207
+ {
208
+ source: "/api/:path*",
209
+ has: [
210
+ { type: "header", key: "authorization" },
211
+ ],
212
+ },
213
+ ],
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 로깅
220
+
221
+ ```typescript
222
+ export function middleware(request: NextRequest) {
223
+ const start = Date.now()
224
+
225
+ const response = NextResponse.next()
226
+
227
+ const duration = Date.now() - start
228
+
229
+ console.log({
230
+ method: request.method,
231
+ url: request.url,
232
+ duration: `${duration}ms`,
233
+ status: response.status,
234
+ })
235
+
236
+ return response
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Rate Limiting
243
+
244
+ ```typescript
245
+ import { NextResponse } from "next/server"
246
+ import type { NextRequest } from "next/server"
247
+
248
+ const rateLimit = new Map<string, { count: number; resetAt: number }>()
249
+
250
+ const LIMIT = 10 // 10 requests
251
+ const WINDOW = 60 * 1000 // 1분
252
+
253
+ export function middleware(request: NextRequest) {
254
+ const ip = request.ip || "unknown"
255
+ const now = Date.now()
256
+
257
+ const record = rateLimit.get(ip)
258
+
259
+ if (!record || now > record.resetAt) {
260
+ rateLimit.set(ip, { count: 1, resetAt: now + WINDOW })
261
+ return NextResponse.next()
262
+ }
263
+
264
+ if (record.count >= LIMIT) {
265
+ return NextResponse.json(
266
+ { error: "Too many requests" },
267
+ { status: 429 }
268
+ )
269
+ }
270
+
271
+ record.count++
272
+ return NextResponse.next()
273
+ }
274
+
275
+ export const config = {
276
+ matcher: "/api/:path*",
277
+ }
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Geolocation
283
+
284
+ ```typescript
285
+ export function middleware(request: NextRequest) {
286
+ const country = request.geo?.country || "US"
287
+ const city = request.geo?.city || "Unknown"
288
+
289
+ const response = NextResponse.next()
290
+ response.headers.set("X-Country", country)
291
+ response.headers.set("X-City", city)
292
+
293
+ return response
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## A/B 테스팅
300
+
301
+ ```typescript
302
+ import { NextResponse } from "next/server"
303
+ import type { NextRequest } from "next/server"
304
+
305
+ export function middleware(request: NextRequest) {
306
+ const bucket = request.cookies.get("bucket")
307
+
308
+ if (!bucket) {
309
+ const newBucket = Math.random() > 0.5 ? "A" : "B"
310
+ const response = NextResponse.next()
311
+
312
+ response.cookies.set("bucket", newBucket)
313
+
314
+ if (newBucket === "B") {
315
+ return NextResponse.rewrite(new URL("/variant-b", request.url))
316
+ }
317
+
318
+ return response
319
+ }
320
+
321
+ if (bucket.value === "B") {
322
+ return NextResponse.rewrite(new URL("/variant-b", request.url))
323
+ }
324
+
325
+ return NextResponse.next()
326
+ }
327
+
328
+ export const config = {
329
+ matcher: "/",
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## 베스트 프랙티스
336
+
337
+ ### ✅ DO
338
+
339
+ ```typescript
340
+ // 1. 가벼운 로직만
341
+ export function middleware(request: NextRequest) {
342
+ const token = request.cookies.get("token")
343
+
344
+ if (!token) {
345
+ return NextResponse.redirect(new URL("/login", request.url))
346
+ }
347
+
348
+ return NextResponse.next()
349
+ }
350
+
351
+ // 2. matcher 설정
352
+ export const config = {
353
+ matcher: ["/dashboard/:path*"],
354
+ }
355
+ ```
356
+
357
+ ### ❌ DON'T
358
+
359
+ ```typescript
360
+ // 1. 무거운 DB 쿼리
361
+ export async function middleware(request: NextRequest) {
362
+ // ❌ 미들웨어에서 DB 쿼리 금지
363
+ const user = await prisma.user.findUnique({ where: { id: "..." } })
364
+ return NextResponse.next()
365
+ }
366
+
367
+ // 2. matcher 없이 모든 요청 처리
368
+ export function middleware(request: NextRequest) {
369
+ // ❌ 성능 저하
370
+ return NextResponse.next()
371
+ }
372
+ ```
373
+
374
+ ---
375
+
376
+ ## NextAuth.js와 함께 사용
377
+
378
+ ```typescript
379
+ // middleware.ts
380
+ export { default } from "next-auth/middleware"
381
+
382
+ export const config = {
383
+ matcher: ["/dashboard/:path*", "/profile/:path*"],
384
+ }
385
+ ```
386
+
387
+ ---
388
+
389
+ ## 참조
390
+
391
+ - [Next.js Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)