@kood/claude-code 0.3.6 → 0.3.8
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/.claude/agents/code-reviewer.md +124 -124
- package/templates/.claude/agents/dependency-manager.md +85 -85
- package/templates/.claude/agents/deployment-validator.md +56 -56
- package/templates/.claude/agents/git-operator.md +64 -64
- package/templates/.claude/agents/implementation-executor.md +95 -95
- package/templates/.claude/agents/ko-to-en-translator.md +74 -0
- package/templates/.claude/agents/lint-fixer.md +78 -78
- package/templates/.claude/agents/refactor-advisor.md +122 -122
- package/templates/.claude/commands/agent-creator.md +185 -185
- package/templates/.claude/commands/bug-fix.md +193 -193
- package/templates/.claude/commands/command-creator.md +54 -54
- package/templates/.claude/commands/docs-creator.md +57 -57
- package/templates/.claude/commands/docs-refactor.md +26 -26
- package/templates/.claude/commands/execute.md +12 -12
- package/templates/.claude/commands/git-all.md +32 -32
- package/templates/.claude/commands/git-session.md +42 -42
- package/templates/.claude/commands/git.md +34 -34
- package/templates/.claude/commands/lint-fix.md +138 -138
- package/templates/.claude/commands/lint-init.md +61 -61
- package/templates/.claude/commands/plan.md +260 -260
- package/templates/.claude/commands/prd.md +24 -24
- package/templates/.claude/commands/pre-deploy.md +109 -109
- package/templates/.claude/commands/refactor.md +147 -147
- package/templates/.claude/commands/version-update.md +17 -17
- package/templates/hono/CLAUDE.md +27 -27
- 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 +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/npx/CLAUDE.md +37 -37
- 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 +53 -49
- 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 +63 -35
- package/templates/tanstack-start/docs/guides/routes.md +61 -42
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/better-auth/index.md +68 -68
- 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/index.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/t3-env/index.md +21 -160
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +6 -6
- 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/index.md +18 -18
- 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/index.md +15 -15
- 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/index.md +8 -8
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# App Router
|
|
2
|
+
|
|
3
|
+
> File-based routing system
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## File Structure and Routing
|
|
8
|
+
|
|
9
|
+
### Basic Rules
|
|
10
|
+
|
|
11
|
+
| File | Route | Description |
|
|
12
|
+
|------|-------|-------------|
|
|
13
|
+
| `app/page.tsx` | `/` | Home page |
|
|
14
|
+
| `app/about/page.tsx` | `/about` | About page |
|
|
15
|
+
| `app/blog/[slug]/page.tsx` | `/blog/:slug` | Dynamic route |
|
|
16
|
+
| `app/shop/[...slug]/page.tsx` | `/shop/*` | Catch-all |
|
|
17
|
+
| `app/docs/[[...slug]]/page.tsx` | `/docs/*` | Optional catch-all |
|
|
18
|
+
|
|
19
|
+
### Example
|
|
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 (required)
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// app/layout.tsx
|
|
45
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
46
|
+
return (
|
|
47
|
+
<html lang="en">
|
|
48
|
+
<body>{children}</body>
|
|
49
|
+
</html>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Nested 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
|
+
**Features:**
|
|
69
|
+
- Nestable (parent → child order)
|
|
70
|
+
- Persists without re-rendering
|
|
71
|
+
- Receives `children` as props
|
|
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
|
+
**Purpose:**
|
|
91
|
+
- Group folders without affecting URLs
|
|
92
|
+
- Apply different layouts
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Dynamic Routes
|
|
97
|
+
|
|
98
|
+
### Single Parameter
|
|
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
|
+
// Static generation (at build time)
|
|
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
|
+
## Parallel Routes
|
|
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
|
+
## Intercepting Routes
|
|
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
|
+
**Conventions:**
|
|
176
|
+
- `(.)` - same level
|
|
177
|
+
- `(..)` - one level up
|
|
178
|
+
- `(..)(..)` - two levels up
|
|
179
|
+
- `(...)` - from root
|
|
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
|
+
## Special Files
|
|
207
|
+
|
|
208
|
+
| File | Purpose |
|
|
209
|
+
|------|---------|
|
|
210
|
+
| `loading.tsx` | Suspense fallback |
|
|
211
|
+
| `error.tsx` | Error Boundary |
|
|
212
|
+
| `not-found.tsx` | 404 page |
|
|
213
|
+
| `template.tsx` | Re-rendering Layout |
|
|
214
|
+
| `default.tsx` | Parallel route fallback |
|
|
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>An error occurred</h2>
|
|
235
|
+
<button onClick={reset}>Try again</button>
|
|
236
|
+
</div>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Navigation
|
|
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() // current path
|
|
254
|
+
const searchParams = useSearchParams() // query parameters
|
|
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
|
+
## References
|
|
268
|
+
|
|
269
|
+
- [Next.js App Router Official Docs](https://nextjs.org/docs/app)
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Caching
|
|
2
|
+
|
|
3
|
+
> Next.js caching strategies
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Cache Levels
|
|
8
|
+
|
|
9
|
+
| Level | Description |
|
|
10
|
+
|-------|-------------|
|
|
11
|
+
| **Request Memoization** | Deduplicate same requests (React) |
|
|
12
|
+
| **Data Cache** | Server data cache (persistent) |
|
|
13
|
+
| **Full Route Cache** | Static rendering at build time |
|
|
14
|
+
| **Router Cache** | Client-side router cache |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Request Memoization
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Same requests execute only once
|
|
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 executes
|
|
29
|
+
const user2 = await getUser("1") // cache used (deduplicated)
|
|
30
|
+
|
|
31
|
+
return <div>{user1.name}</div>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Data Cache (fetch)
|
|
38
|
+
|
|
39
|
+
### Default (cached)
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Cached by default
|
|
43
|
+
const res = await fetch("https://api.example.com/posts")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Disable Cache
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Fetch fresh data on every request
|
|
50
|
+
const res = await fetch("https://api.example.com/posts", {
|
|
51
|
+
cache: "no-store",
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Revalidate (time-based)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Revalidate every 60 seconds
|
|
59
|
+
const res = await fetch("https://api.example.com/posts", {
|
|
60
|
+
next: { revalidate: 60 },
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Tag-based Cache
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Set tag
|
|
68
|
+
const res = await fetch("https://api.example.com/posts", {
|
|
69
|
+
next: { tags: ["posts"] },
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Invalidate tag in 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") // Invalidate "posts" tag cache
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## unstable_cache (function caching)
|
|
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"], // cache key
|
|
94
|
+
{
|
|
95
|
+
revalidate: 60, // 60 seconds
|
|
96
|
+
tags: ["posts"], // tags
|
|
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
|
+
// Invalidate specific path cache
|
|
119
|
+
revalidatePath("/posts")
|
|
120
|
+
revalidatePath(`/posts/${post.id}`)
|
|
121
|
+
|
|
122
|
+
// Invalidate all caches including layout
|
|
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
|
+
// Invalidate all caches with "posts" tag
|
|
142
|
+
revalidateTag("posts")
|
|
143
|
+
|
|
144
|
+
return post
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Full Route Cache (static rendering)
|
|
151
|
+
|
|
152
|
+
### Static Page
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Generated at build time (default)
|
|
156
|
+
export default async function PostsPage() {
|
|
157
|
+
const posts = await prisma.post.findMany()
|
|
158
|
+
return <PostsList posts={posts} />
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Dynamic Page (disable cache)
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Render on every request
|
|
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 (time-based)
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Regenerate every 60 seconds
|
|
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
|
+
// Force dynamic rendering
|
|
194
|
+
export const dynamic = "force-dynamic" // "auto" | "force-static" | "error"
|
|
195
|
+
|
|
196
|
+
// Revalidate interval (seconds)
|
|
197
|
+
export const revalidate = 60 // false | 0 | number
|
|
198
|
+
|
|
199
|
+
// Runtime configuration
|
|
200
|
+
export const runtime = "nodejs" // "edge"
|
|
201
|
+
|
|
202
|
+
// Maximum execution time (seconds)
|
|
203
|
+
export const maxDuration = 60
|
|
204
|
+
|
|
205
|
+
export default async function PostsPage() {
|
|
206
|
+
// ...
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Router Cache (client-side)
|
|
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") // Use cached page
|
|
226
|
+
router.refresh() // Force refresh
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
Go to Posts
|
|
230
|
+
</button>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## generateStaticParams (dynamic routes)
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// app/posts/[id]/page.tsx
|
|
241
|
+
|
|
242
|
+
// List of pages to generate at build time
|
|
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
|
+
## Caching Flow
|
|
257
|
+
|
|
258
|
+
### Static Page
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
1. Render at build time
|
|
262
|
+
2. Save to Full Route Cache
|
|
263
|
+
3. Subsequent requests use cache
|
|
264
|
+
4. Regenerate after revalidate time
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Dynamic Page
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
1. Render on every request
|
|
271
|
+
2. No cache
|
|
272
|
+
3. Update data with Server Actions
|
|
273
|
+
4. Invalidate only specific paths with revalidatePath
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Cache Invalidation Strategies
|
|
279
|
+
|
|
280
|
+
### Time-based
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Regenerate every 60 seconds
|
|
284
|
+
export const revalidate = 60
|
|
285
|
+
|
|
286
|
+
const res = await fetch("...", { next: { revalidate: 60 } })
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### On-demand (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
|
+
// Path invalidation
|
|
300
|
+
revalidatePath(`/posts/${id}`)
|
|
301
|
+
|
|
302
|
+
// Tag invalidation
|
|
303
|
+
revalidateTag("posts")
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Best Practices
|
|
310
|
+
|
|
311
|
+
### ✅ DO
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// 1. Use default cache for static data
|
|
315
|
+
const posts = await fetch("https://api.example.com/posts")
|
|
316
|
+
|
|
317
|
+
// 2. Use no-store for dynamic data
|
|
318
|
+
const user = await fetch("https://api.example.com/user", {
|
|
319
|
+
cache: "no-store",
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// 3. Tag-based invalidation
|
|
323
|
+
const posts = await fetch("...", { next: { tags: ["posts"] } })
|
|
324
|
+
revalidateTag("posts")
|
|
325
|
+
|
|
326
|
+
// 4. Function caching
|
|
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. Caching sensitive data
|
|
338
|
+
const user = await fetch("/api/user") // ❌ Don't cache personal data
|
|
339
|
+
|
|
340
|
+
// 2. Excessive revalidatePath
|
|
341
|
+
revalidatePath("/") // ❌ Invalidates entire site
|
|
342
|
+
|
|
343
|
+
// 3. Short revalidate interval
|
|
344
|
+
export const revalidate = 1 // ❌ Increases load
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## References
|
|
350
|
+
|
|
351
|
+
- [Next.js Caching](https://nextjs.org/docs/app/building-your-application/caching)
|