@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.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/nextjs/CLAUDE.md +228 -0
- package/templates/nextjs/docs/design.md +558 -0
- package/templates/nextjs/docs/guides/conventions.md +343 -0
- package/templates/nextjs/docs/guides/getting-started.md +367 -0
- package/templates/nextjs/docs/guides/routes.md +342 -0
- package/templates/nextjs/docs/library/better-auth/index.md +541 -0
- package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
- package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
- package/templates/nextjs/docs/library/nextjs/index.md +291 -0
- package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
- package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
- package/templates/nextjs/docs/library/prisma/config.md +77 -0
- package/templates/nextjs/docs/library/prisma/crud.md +90 -0
- package/templates/nextjs/docs/library/prisma/index.md +73 -0
- package/templates/nextjs/docs/library/prisma/relations.md +69 -0
- package/templates/nextjs/docs/library/prisma/schema.md +98 -0
- package/templates/nextjs/docs/library/prisma/setup.md +49 -0
- package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
- package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
- package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
- package/templates/nextjs/docs/library/zod/index.md +56 -0
- package/templates/nextjs/docs/library/zod/transforms.md +51 -0
- package/templates/nextjs/docs/library/zod/validation.md +70 -0
- package/templates/tanstack-start/CLAUDE.md +7 -3
- package/templates/tanstack-start/docs/guides/hooks.md +28 -0
- package/templates/tanstack-start/docs/guides/routes.md +29 -10
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Routes
|
|
2
|
+
|
|
3
|
+
> Next.js App Router 라우팅 패턴
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 기본 라우팅
|
|
8
|
+
|
|
9
|
+
| 파일 | 라우트 |
|
|
10
|
+
|------|--------|
|
|
11
|
+
| `app/page.tsx` | `/` |
|
|
12
|
+
| `app/about/page.tsx` | `/about` |
|
|
13
|
+
| `app/blog/page.tsx` | `/blog` |
|
|
14
|
+
| `app/blog/[slug]/page.tsx` | `/blog/:slug` |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 동적 라우트
|
|
19
|
+
|
|
20
|
+
### 단일 파라미터
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// app/posts/[id]/page.tsx
|
|
24
|
+
interface PageProps {
|
|
25
|
+
params: { id: string }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default async function PostPage({ params }: PageProps) {
|
|
29
|
+
const post = await prisma.post.findUnique({ where: { id: params.id } })
|
|
30
|
+
|
|
31
|
+
if (!post) notFound()
|
|
32
|
+
|
|
33
|
+
return <article>{post.title}</article>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 정적 생성
|
|
37
|
+
export async function generateStaticParams() {
|
|
38
|
+
const posts = await prisma.post.findMany({ select: { id: true } })
|
|
39
|
+
return posts.map(post => ({ id: post.id }))
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Catch-all
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// app/docs/[...slug]/page.tsx
|
|
47
|
+
interface PageProps {
|
|
48
|
+
params: { slug: string[] }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function DocsPage({ params }: PageProps) {
|
|
52
|
+
// /docs/a/b/c → params.slug = ["a", "b", "c"]
|
|
53
|
+
const path = params.slug.join("/")
|
|
54
|
+
return <div>{path}</div>
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 레이아웃
|
|
61
|
+
|
|
62
|
+
### Root Layout (필수)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// app/layout.tsx
|
|
66
|
+
import { Providers } from "./providers"
|
|
67
|
+
|
|
68
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
69
|
+
return (
|
|
70
|
+
<html lang="ko">
|
|
71
|
+
<body>
|
|
72
|
+
<Providers>{children}</Providers>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 중첩 Layout
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// app/dashboard/layout.tsx
|
|
83
|
+
import { auth } from "@/lib/auth"
|
|
84
|
+
import { headers } from "next/headers"
|
|
85
|
+
import { redirect } from "next/navigation"
|
|
86
|
+
|
|
87
|
+
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
88
|
+
// 인증 체크
|
|
89
|
+
const session = await auth.api.getSession({ headers: headers() })
|
|
90
|
+
if (!session?.user) redirect("/login")
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<nav>Dashboard Nav</nav>
|
|
95
|
+
<main>{children}</main>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Route Groups
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
app/
|
|
107
|
+
├── (marketing)/
|
|
108
|
+
│ ├── layout.tsx # Marketing layout
|
|
109
|
+
│ ├── page.tsx # /
|
|
110
|
+
│ └── about/
|
|
111
|
+
│ └── page.tsx # /about
|
|
112
|
+
└── (shop)/
|
|
113
|
+
├── layout.tsx # Shop layout
|
|
114
|
+
└── products/
|
|
115
|
+
└── page.tsx # /products
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**용도:** URL에 영향 없이 다른 레이아웃 적용
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Loading & Error
|
|
123
|
+
|
|
124
|
+
### Loading UI
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// app/posts/loading.tsx
|
|
128
|
+
export default function Loading() {
|
|
129
|
+
return <div>Loading posts...</div>
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Error UI
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// app/posts/error.tsx
|
|
137
|
+
"use client"
|
|
138
|
+
|
|
139
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
140
|
+
return (
|
|
141
|
+
<div>
|
|
142
|
+
<h2>오류 발생: {error.message}</h2>
|
|
143
|
+
<button onClick={reset}>다시 시도</button>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Not Found
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// app/posts/[id]/not-found.tsx
|
|
153
|
+
export default function NotFound() {
|
|
154
|
+
return <div>게시글을 찾을 수 없습니다</div>
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Metadata
|
|
161
|
+
|
|
162
|
+
### 정적
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// app/about/page.tsx
|
|
166
|
+
import type { Metadata } from "next"
|
|
167
|
+
|
|
168
|
+
export const metadata: Metadata = {
|
|
169
|
+
title: "About Us",
|
|
170
|
+
description: "Learn more about our company",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default function AboutPage() {
|
|
174
|
+
return <div>About</div>
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 동적
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// app/posts/[id]/page.tsx
|
|
182
|
+
import type { Metadata } from "next"
|
|
183
|
+
|
|
184
|
+
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
|
|
185
|
+
const post = await prisma.post.findUnique({ where: { id: params.id } })
|
|
186
|
+
|
|
187
|
+
if (!post) {
|
|
188
|
+
return {
|
|
189
|
+
title: "Post Not Found",
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
title: post.title,
|
|
195
|
+
description: post.excerpt,
|
|
196
|
+
openGraph: {
|
|
197
|
+
title: post.title,
|
|
198
|
+
description: post.excerpt,
|
|
199
|
+
images: [post.image],
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 네비게이션
|
|
208
|
+
|
|
209
|
+
### Link
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import Link from "next/link"
|
|
213
|
+
|
|
214
|
+
export function Navigation() {
|
|
215
|
+
return (
|
|
216
|
+
<nav>
|
|
217
|
+
<Link href="/">Home</Link>
|
|
218
|
+
<Link href="/about">About</Link>
|
|
219
|
+
<Link href="/posts">Posts</Link>
|
|
220
|
+
</nav>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### useRouter
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
"use client"
|
|
229
|
+
|
|
230
|
+
import { useRouter } from "next/navigation"
|
|
231
|
+
|
|
232
|
+
export function LoginButton() {
|
|
233
|
+
const router = useRouter()
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<button onClick={() => router.push("/login")}>
|
|
237
|
+
Login
|
|
238
|
+
</button>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 인증 보호
|
|
246
|
+
|
|
247
|
+
### Layout에서 체크
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// app/dashboard/layout.tsx
|
|
251
|
+
import { auth } from "@/lib/auth"
|
|
252
|
+
import { headers } from "next/headers"
|
|
253
|
+
import { redirect } from "next/navigation"
|
|
254
|
+
|
|
255
|
+
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
256
|
+
const session = await auth.api.getSession({ headers: headers() })
|
|
257
|
+
|
|
258
|
+
if (!session?.user) {
|
|
259
|
+
redirect("/login")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return <div>{children}</div>
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Middleware에서 체크
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// middleware.ts
|
|
270
|
+
import { auth } from "@/lib/auth"
|
|
271
|
+
import { NextResponse } from "next/server"
|
|
272
|
+
import type { NextRequest } from "next/server"
|
|
273
|
+
|
|
274
|
+
export async function middleware(request: NextRequest) {
|
|
275
|
+
const session = await auth.api.getSession({ headers: request.headers })
|
|
276
|
+
|
|
277
|
+
if (!session?.user) {
|
|
278
|
+
return NextResponse.redirect(new URL("/login", request.url))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return NextResponse.next()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const config = {
|
|
285
|
+
matcher: ["/dashboard/:path*", "/profile/:path*"],
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 베스트 프랙티스
|
|
292
|
+
|
|
293
|
+
### ✅ DO
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// 1. Server Component에서 직접 데이터 페칭
|
|
297
|
+
export default async function PostsPage() {
|
|
298
|
+
const posts = await prisma.post.findMany()
|
|
299
|
+
return <PostsList posts={posts} />
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 2. 레이아웃에서 인증 체크
|
|
303
|
+
export default async function DashboardLayout({ children }) {
|
|
304
|
+
const session = await auth.api.getSession({ headers: headers() })
|
|
305
|
+
if (!session?.user) redirect("/login")
|
|
306
|
+
return <div>{children}</div>
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 3. generateStaticParams로 정적 생성
|
|
310
|
+
export async function generateStaticParams() {
|
|
311
|
+
const posts = await prisma.post.findMany({ select: { id: true } })
|
|
312
|
+
return posts.map(post => ({ id: post.id }))
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### ❌ DON'T
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// 1. Client Component에서 async/await
|
|
320
|
+
"use client"
|
|
321
|
+
export default async function PostsPage() { // ❌
|
|
322
|
+
const posts = await prisma.post.findMany()
|
|
323
|
+
return <PostsList posts={posts} />
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 2. 페이지마다 인증 체크 반복
|
|
327
|
+
export default async function Page1() {
|
|
328
|
+
const session = await auth.api.getSession({ headers: headers() }) // ❌ 중복
|
|
329
|
+
if (!session) redirect("/login")
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 3. 하드코딩된 경로
|
|
333
|
+
<Link href="/posts/123">Post</Link> // ❌ 하드코딩
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 참조
|
|
339
|
+
|
|
340
|
+
- [App Router](../library/nextjs/app-router.md)
|
|
341
|
+
- [Server Actions](server-actions.md)
|
|
342
|
+
- [Client Components](client-components.md)
|