@kood/claude-code 0.3.6 → 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 (34) 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/guides/hooks.md +28 -0
  34. package/templates/tanstack-start/docs/guides/routes.md +29 -10
@@ -0,0 +1,269 @@
1
+ # App Router
2
+
3
+ > 파일 기반 라우팅 시스템
4
+
5
+ ---
6
+
7
+ ## 파일 구조와 라우팅
8
+
9
+ ### 기본 규칙
10
+
11
+ | 파일 | 라우트 | 설명 |
12
+ |------|--------|------|
13
+ | `app/page.tsx` | `/` | 홈 페이지 |
14
+ | `app/about/page.tsx` | `/about` | About 페이지 |
15
+ | `app/blog/[slug]/page.tsx` | `/blog/:slug` | 동적 라우트 |
16
+ | `app/shop/[...slug]/page.tsx` | `/shop/*` | Catch-all |
17
+ | `app/docs/[[...slug]]/page.tsx` | `/docs/*` | Optional catch-all |
18
+
19
+ ### 예시
20
+
21
+ ```typescript
22
+ // app/blog/[slug]/page.tsx
23
+ interface PageProps {
24
+ params: { slug: string }
25
+ searchParams: { [key: string]: string | string[] | undefined }
26
+ }
27
+
28
+ export default async function BlogPost({ params, searchParams }: PageProps) {
29
+ const post = await prisma.post.findUnique({ where: { slug: params.slug } })
30
+
31
+ if (!post) notFound()
32
+
33
+ return <article>{post.content}</article>
34
+ }
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Layouts
40
+
41
+ ### Root Layout (필수)
42
+
43
+ ```typescript
44
+ // app/layout.tsx
45
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
46
+ return (
47
+ <html lang="ko">
48
+ <body>{children}</body>
49
+ </html>
50
+ )
51
+ }
52
+ ```
53
+
54
+ ### 중첩 Layout
55
+
56
+ ```typescript
57
+ // app/dashboard/layout.tsx
58
+ export default function DashboardLayout({ children }: { children: React.ReactNode }) {
59
+ return (
60
+ <div>
61
+ <nav>Dashboard Nav</nav>
62
+ <main>{children}</main>
63
+ </div>
64
+ )
65
+ }
66
+ ```
67
+
68
+ **특징:**
69
+ - 중첩 가능 (부모 → 자식 순서)
70
+ - 리렌더링 없이 유지됨
71
+ - props로 `children` 받음
72
+
73
+ ---
74
+
75
+ ## Route Groups
76
+
77
+ ```
78
+ app/
79
+ ├── (marketing)/
80
+ │ ├── layout.tsx # Marketing layout
81
+ │ ├── page.tsx # /
82
+ │ └── about/
83
+ │ └── page.tsx # /about
84
+ └── (shop)/
85
+ ├── layout.tsx # Shop layout
86
+ └── products/
87
+ └── page.tsx # /products
88
+ ```
89
+
90
+ **용도:**
91
+ - URL에 영향 없이 폴더 그룹화
92
+ - 다른 레이아웃 적용
93
+
94
+ ---
95
+
96
+ ## 동적 라우트
97
+
98
+ ### 단일 파라미터
99
+
100
+ ```typescript
101
+ // app/posts/[id]/page.tsx
102
+ export default async function PostPage({ params }: { params: { id: string } }) {
103
+ const post = await prisma.post.findUnique({ where: { id: params.id } })
104
+ return <article>{post.title}</article>
105
+ }
106
+
107
+ // 정적 생성 (빌드 시)
108
+ export async function generateStaticParams() {
109
+ const posts = await prisma.post.findMany()
110
+ return posts.map(post => ({ id: post.id }))
111
+ }
112
+ ```
113
+
114
+ ### Catch-all
115
+
116
+ ```typescript
117
+ // app/docs/[...slug]/page.tsx
118
+ export default function DocsPage({ params }: { params: { slug: string[] } }) {
119
+ // /docs/a/b/c → params.slug = ["a", "b", "c"]
120
+ return <div>{params.slug.join("/")}</div>
121
+ }
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 병렬 라우트
127
+
128
+ ```
129
+ app/
130
+ ├── @analytics/
131
+ │ └── page.tsx
132
+ ├── @team/
133
+ │ └── page.tsx
134
+ └── layout.tsx
135
+ ```
136
+
137
+ ```typescript
138
+ // app/layout.tsx
139
+ export default function Layout({
140
+ children,
141
+ analytics,
142
+ team,
143
+ }: {
144
+ children: React.ReactNode
145
+ analytics: React.ReactNode
146
+ team: React.ReactNode
147
+ }) {
148
+ return (
149
+ <>
150
+ {children}
151
+ {analytics}
152
+ {team}
153
+ </>
154
+ )
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 인터셉팅 라우트
161
+
162
+ ```
163
+ app/
164
+ ├── feed/
165
+ │ └── page.tsx
166
+ ├── photo/
167
+ │ └── [id]/
168
+ │ └── page.tsx
169
+ └── @modal/
170
+ └── (.)photo/
171
+ └── [id]/
172
+ └── page.tsx
173
+ ```
174
+
175
+ **컨벤션:**
176
+ - `(.)` - 같은 레벨
177
+ - `(..)` - 한 단계 위
178
+ - `(..)(..)` - 두 단계 위
179
+ - `(...)` - 루트부터
180
+
181
+ ---
182
+
183
+ ## Metadata
184
+
185
+ ```typescript
186
+ // app/blog/[slug]/page.tsx
187
+ import type { Metadata } from "next"
188
+
189
+ export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
190
+ const post = await prisma.post.findUnique({ where: { slug: params.slug } })
191
+
192
+ return {
193
+ title: post.title,
194
+ description: post.excerpt,
195
+ openGraph: {
196
+ title: post.title,
197
+ description: post.excerpt,
198
+ images: [post.image],
199
+ },
200
+ }
201
+ }
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 특수 파일
207
+
208
+ | 파일 | 용도 |
209
+ |------|------|
210
+ | `loading.tsx` | Suspense 폴백 |
211
+ | `error.tsx` | Error Boundary |
212
+ | `not-found.tsx` | 404 페이지 |
213
+ | `template.tsx` | 리렌더링되는 Layout |
214
+ | `default.tsx` | 병렬 라우트 폴백 |
215
+
216
+ ### Loading UI
217
+
218
+ ```typescript
219
+ // app/dashboard/loading.tsx
220
+ export default function Loading() {
221
+ return <div>Loading...</div>
222
+ }
223
+ ```
224
+
225
+ ### Error UI
226
+
227
+ ```typescript
228
+ // app/dashboard/error.tsx
229
+ "use client"
230
+
231
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
232
+ return (
233
+ <div>
234
+ <h2>오류 발생</h2>
235
+ <button onClick={reset}>다시 시도</button>
236
+ </div>
237
+ )
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## 네비게이션
244
+
245
+ ```typescript
246
+ "use client"
247
+
248
+ import { useRouter, usePathname, useSearchParams } from "next/navigation"
249
+ import Link from "next/link"
250
+
251
+ export function Navigation() {
252
+ const router = useRouter()
253
+ const pathname = usePathname() // 현재 경로
254
+ const searchParams = useSearchParams() // 쿼리 파라미터
255
+
256
+ return (
257
+ <>
258
+ <Link href="/about">About</Link>
259
+ <button onClick={() => router.push("/posts")}>Go to Posts</button>
260
+ </>
261
+ )
262
+ }
263
+ ```
264
+
265
+ ---
266
+
267
+ ## 참조
268
+
269
+ - [Next.js App Router 공식 문서](https://nextjs.org/docs/app)
@@ -0,0 +1,351 @@
1
+ # Caching
2
+
3
+ > Next.js 캐싱 전략
4
+
5
+ ---
6
+
7
+ ## 캐시 레벨
8
+
9
+ | 레벨 | 설명 |
10
+ |------|------|
11
+ | **Request Memoization** | 같은 요청 중복 제거 (React) |
12
+ | **Data Cache** | 서버 데이터 캐시 (영구) |
13
+ | **Full Route Cache** | 빌드 시 정적 렌더링 |
14
+ | **Router Cache** | 클라이언트 라우터 캐시 |
15
+
16
+ ---
17
+
18
+ ## Request Memoization
19
+
20
+ ```typescript
21
+ // 같은 요청은 한 번만 실행됨
22
+ async function getUser(id: string) {
23
+ const res = await fetch(`https://api.example.com/users/${id}`)
24
+ return res.json()
25
+ }
26
+
27
+ export default async function Page() {
28
+ const user1 = await getUser("1") // fetch 실행
29
+ const user2 = await getUser("1") // 캐시 사용 (중복 제거)
30
+
31
+ return <div>{user1.name}</div>
32
+ }
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Data Cache (fetch)
38
+
39
+ ### 기본 (캐시 사용)
40
+
41
+ ```typescript
42
+ // 기본적으로 캐시됨
43
+ const res = await fetch("https://api.example.com/posts")
44
+ ```
45
+
46
+ ### 캐시 비활성화
47
+
48
+ ```typescript
49
+ // 매 요청마다 새로 가져옴
50
+ const res = await fetch("https://api.example.com/posts", {
51
+ cache: "no-store",
52
+ })
53
+ ```
54
+
55
+ ### Revalidate (시간 기반)
56
+
57
+ ```typescript
58
+ // 60초마다 재검증
59
+ const res = await fetch("https://api.example.com/posts", {
60
+ next: { revalidate: 60 },
61
+ })
62
+ ```
63
+
64
+ ### Tag 기반 캐시
65
+
66
+ ```typescript
67
+ // 태그 설정
68
+ const res = await fetch("https://api.example.com/posts", {
69
+ next: { tags: ["posts"] },
70
+ })
71
+
72
+ // Server Action에서 태그 무효화
73
+ "use server"
74
+ import { revalidateTag } from "next/cache"
75
+
76
+ export async function createPost(data: PostInput) {
77
+ await prisma.post.create({ data })
78
+ revalidateTag("posts") // "posts" 태그 캐시 무효화
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## unstable_cache (함수 캐싱)
85
+
86
+ ```typescript
87
+ import { unstable_cache } from "next/cache"
88
+
89
+ const getCachedPosts = unstable_cache(
90
+ async () => {
91
+ return prisma.post.findMany()
92
+ },
93
+ ["posts"], // 캐시 키
94
+ {
95
+ revalidate: 60, // 60초
96
+ tags: ["posts"], // 태그
97
+ }
98
+ )
99
+
100
+ export default async function PostsPage() {
101
+ const posts = await getCachedPosts()
102
+ return <PostsList posts={posts} />
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## revalidatePath
109
+
110
+ ```typescript
111
+ "use server"
112
+
113
+ import { revalidatePath } from "next/cache"
114
+
115
+ export async function createPost(data: PostInput) {
116
+ const post = await prisma.post.create({ data })
117
+
118
+ // 특정 경로 캐시 무효화
119
+ revalidatePath("/posts")
120
+ revalidatePath(`/posts/${post.id}`)
121
+
122
+ // 레이아웃 포함 모든 캐시 무효화
123
+ revalidatePath("/posts", "layout")
124
+
125
+ return post
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## revalidateTag
132
+
133
+ ```typescript
134
+ "use server"
135
+
136
+ import { revalidateTag } from "next/cache"
137
+
138
+ export async function createPost(data: PostInput) {
139
+ const post = await prisma.post.create({ data })
140
+
141
+ // "posts" 태그가 붙은 모든 캐시 무효화
142
+ revalidateTag("posts")
143
+
144
+ return post
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Full Route Cache (정적 렌더링)
151
+
152
+ ### 정적 페이지
153
+
154
+ ```typescript
155
+ // 빌드 시 생성 (기본)
156
+ export default async function PostsPage() {
157
+ const posts = await prisma.post.findMany()
158
+ return <PostsList posts={posts} />
159
+ }
160
+ ```
161
+
162
+ ### 동적 페이지 (캐시 비활성화)
163
+
164
+ ```typescript
165
+ // 매 요청마다 렌더링
166
+ export const dynamic = "force-dynamic"
167
+
168
+ export default async function PostsPage() {
169
+ const posts = await prisma.post.findMany()
170
+ return <PostsList posts={posts} />
171
+ }
172
+ ```
173
+
174
+ ### Revalidate (시간 기반)
175
+
176
+ ```typescript
177
+ // 60초마다 재생성
178
+ export const revalidate = 60
179
+
180
+ export default async function PostsPage() {
181
+ const posts = await prisma.post.findMany()
182
+ return <PostsList posts={posts} />
183
+ }
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Route Segment Config
189
+
190
+ ```typescript
191
+ // app/posts/page.tsx
192
+
193
+ // 동적 렌더링 강제
194
+ export const dynamic = "force-dynamic" // "auto" | "force-static" | "error"
195
+
196
+ // Revalidate 주기 (초)
197
+ export const revalidate = 60 // false | 0 | number
198
+
199
+ // 런타임 설정
200
+ export const runtime = "nodejs" // "edge"
201
+
202
+ // 최대 실행 시간 (초)
203
+ export const maxDuration = 60
204
+
205
+ export default async function PostsPage() {
206
+ // ...
207
+ }
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Router Cache (클라이언트)
213
+
214
+ ```typescript
215
+ "use client"
216
+
217
+ import { useRouter } from "next/navigation"
218
+
219
+ export function Navigation() {
220
+ const router = useRouter()
221
+
222
+ return (
223
+ <button
224
+ onClick={() => {
225
+ router.push("/posts") // 캐시된 페이지 사용
226
+ router.refresh() // 강제 새로고침
227
+ }}
228
+ >
229
+ Go to Posts
230
+ </button>
231
+ )
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## generateStaticParams (동적 라우트)
238
+
239
+ ```typescript
240
+ // app/posts/[id]/page.tsx
241
+
242
+ // 빌드 시 생성할 페이지 목록
243
+ export async function generateStaticParams() {
244
+ const posts = await prisma.post.findMany({ select: { id: true } })
245
+ return posts.map(post => ({ id: post.id }))
246
+ }
247
+
248
+ export default async function PostPage({ params }: { params: { id: string } }) {
249
+ const post = await prisma.post.findUnique({ where: { id: params.id } })
250
+ return <article>{post.title}</article>
251
+ }
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 캐싱 플로우
257
+
258
+ ### 정적 페이지
259
+
260
+ ```
261
+ 1. 빌드 시 렌더링
262
+ 2. Full Route Cache 저장
263
+ 3. 이후 요청은 캐시 사용
264
+ 4. revalidate 시간 후 재생성
265
+ ```
266
+
267
+ ### 동적 페이지
268
+
269
+ ```
270
+ 1. 매 요청마다 렌더링
271
+ 2. 캐시 없음
272
+ 3. Server Actions로 데이터 업데이트
273
+ 4. revalidatePath로 특정 경로만 무효화
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 캐시 무효화 전략
279
+
280
+ ### 시간 기반
281
+
282
+ ```typescript
283
+ // 60초마다 재생성
284
+ export const revalidate = 60
285
+
286
+ const res = await fetch("...", { next: { revalidate: 60 } })
287
+ ```
288
+
289
+ ### 온디맨드 (Server Actions)
290
+
291
+ ```typescript
292
+ "use server"
293
+
294
+ import { revalidatePath, revalidateTag } from "next/cache"
295
+
296
+ export async function updatePost(id: string, data: PostInput) {
297
+ await prisma.post.update({ where: { id }, data })
298
+
299
+ // 경로 무효화
300
+ revalidatePath(`/posts/${id}`)
301
+
302
+ // 태그 무효화
303
+ revalidateTag("posts")
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## 베스트 프랙티스
310
+
311
+ ### ✅ DO
312
+
313
+ ```typescript
314
+ // 1. 정적 데이터는 기본 캐시 사용
315
+ const posts = await fetch("https://api.example.com/posts")
316
+
317
+ // 2. 동적 데이터는 no-store
318
+ const user = await fetch("https://api.example.com/user", {
319
+ cache: "no-store",
320
+ })
321
+
322
+ // 3. 태그 기반 무효화
323
+ const posts = await fetch("...", { next: { tags: ["posts"] } })
324
+ revalidateTag("posts")
325
+
326
+ // 4. 함수 캐싱
327
+ const getCachedData = unstable_cache(
328
+ async () => prisma.post.findMany(),
329
+ ["posts"],
330
+ { revalidate: 60 }
331
+ )
332
+ ```
333
+
334
+ ### ❌ DON'T
335
+
336
+ ```typescript
337
+ // 1. 민감한 데이터 캐싱
338
+ const user = await fetch("/api/user") // ❌ 개인정보 캐싱 금지
339
+
340
+ // 2. 과도한 revalidatePath
341
+ revalidatePath("/") // ❌ 전체 사이트 무효화
342
+
343
+ // 3. 짧은 revalidate
344
+ export const revalidate = 1 // ❌ 부하 증가
345
+ ```
346
+
347
+ ---
348
+
349
+ ## 참조
350
+
351
+ - [Next.js Caching](https://nextjs.org/docs/app/building-your-application/caching)