@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,291 @@
|
|
|
1
|
+
# Next.js - Index
|
|
2
|
+
|
|
3
|
+
> Next.js 15 App Router core concepts
|
|
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
|
+
## Core Concepts
|
|
16
|
+
|
|
17
|
+
| Concept | Description |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| **App Router** | File-based routing (`app/` directory) |
|
|
20
|
+
| **Server Components** | Default components (server rendering) |
|
|
21
|
+
| **Client Components** | Requires `"use client"` declaration |
|
|
22
|
+
| **Server Actions** | `"use server"` functions (type-safe API) |
|
|
23
|
+
| **Route Handlers** | REST API endpoints (`app/api/`) |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Create Project
|
|
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
|
+
### Basic Page
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// app/page.tsx (Server Component - default)
|
|
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
|
+
## File Structure
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
app/
|
|
69
|
+
├── layout.tsx # Root layout (required)
|
|
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
|
+
## Key Files
|
|
85
|
+
|
|
86
|
+
| File | Purpose |
|
|
87
|
+
|------|---------|
|
|
88
|
+
| `layout.tsx` | Shared layout (nestable) |
|
|
89
|
+
| `page.tsx` | Page component |
|
|
90
|
+
| `loading.tsx` | Loading UI (Suspense) |
|
|
91
|
+
| `error.tsx` | Error UI (Error Boundary) |
|
|
92
|
+
| `not-found.tsx` | 404 page |
|
|
93
|
+
| `route.ts` | API endpoint |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Server vs Client Components
|
|
98
|
+
|
|
99
|
+
| Aspect | Server | Client |
|
|
100
|
+
|--------|--------|--------|
|
|
101
|
+
| Declaration | Default | `"use client"` |
|
|
102
|
+
| Data Fetching | ✅ async/await | ❌ (use useQuery) |
|
|
103
|
+
| Hooks | ❌ | ✅ useState, useEffect |
|
|
104
|
+
| Browser API | ❌ | ✅ window, localStorage |
|
|
105
|
+
| Event Handlers | ❌ | ✅ onClick, onChange |
|
|
106
|
+
|
|
107
|
+
**Rules:**
|
|
108
|
+
- Server Components are default → add `"use client"` only when needed
|
|
109
|
+
- Server Components can contain Client Components
|
|
110
|
+
- Client Components cannot contain Server Components (but can receive them as 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
|
+
## Data Fetching
|
|
187
|
+
|
|
188
|
+
### Server Component (recommended)
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
export default async function PostsPage() {
|
|
192
|
+
const posts = await prisma.post.findMany() // Direct DB access
|
|
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
|
+
## Caching
|
|
217
|
+
|
|
218
|
+
| Function | Purpose |
|
|
219
|
+
|----------|---------|
|
|
220
|
+
| `revalidatePath("/posts")` | Invalidate specific path cache |
|
|
221
|
+
| `revalidateTag("posts")` | Invalidate tag-based cache |
|
|
222
|
+
| `unstable_cache()` | Cache function results |
|
|
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") // Invalidate /posts cache
|
|
231
|
+
revalidateTag("posts") // Invalidate "posts" tag cache
|
|
232
|
+
|
|
233
|
+
return post
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Environment Variables
|
|
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
|
+
**Rules:**
|
|
250
|
+
- `NEXT_PUBLIC_*`: Accessible in client
|
|
251
|
+
- Others: Server-only
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Deployment
|
|
256
|
+
|
|
257
|
+
### Vercel (recommended)
|
|
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
|
+
## References
|
|
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
|
+
> Functions executed before request processing
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Basic Usage
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// middleware.ts (root)
|
|
11
|
+
import { NextResponse } from "next/server"
|
|
12
|
+
import type { NextRequest } from "next/server"
|
|
13
|
+
|
|
14
|
+
export function middleware(request: NextRequest) {
|
|
15
|
+
// Execute logic...
|
|
16
|
+
return NextResponse.next()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Matcher configuration
|
|
20
|
+
export const config = {
|
|
21
|
+
matcher: ["/dashboard/:path*", "/api/:path*"],
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Response Types
|
|
28
|
+
|
|
29
|
+
### NextResponse.next()
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// Pass request to next middleware or route
|
|
33
|
+
export function middleware(request: NextRequest) {
|
|
34
|
+
return NextResponse.next()
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### NextResponse.redirect()
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// Redirect to different 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
|
+
// Render different page while keeping URL
|
|
51
|
+
export function middleware(request: NextRequest) {
|
|
52
|
+
return NextResponse.rewrite(new URL("/dashboard/home", request.url))
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
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
|
+
// Redirect to login if no token
|
|
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
|
+
## Cookie Handling
|
|
83
|
+
|
|
84
|
+
### Reading
|
|
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
|
+
### Setting
|
|
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 days
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return response
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Headers Handling
|
|
116
|
+
|
|
117
|
+
### Reading
|
|
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
|
+
### Setting
|
|
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
|
+
## Path-based Processing
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
export function middleware(request: NextRequest) {
|
|
149
|
+
const { pathname } = request.nextUrl
|
|
150
|
+
|
|
151
|
+
// /api/* routes
|
|
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/* routes
|
|
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 Configuration
|
|
176
|
+
|
|
177
|
+
### Array
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
export const config = {
|
|
181
|
+
matcher: ["/dashboard/:path*", "/profile/:path*"],
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Regex
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
export const config = {
|
|
189
|
+
matcher: [
|
|
190
|
+
/*
|
|
191
|
+
* Exclude paths:
|
|
192
|
+
* - _next/static (static files)
|
|
193
|
+
* - _next/image (image optimization)
|
|
194
|
+
* - favicon.ico (favicon)
|
|
195
|
+
*/
|
|
196
|
+
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
197
|
+
],
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Conditional
|
|
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
|
+
## Logging
|
|
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 minute
|
|
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 Testing
|
|
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
|
+
## Best Practices
|
|
336
|
+
|
|
337
|
+
### ✅ DO
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// 1. Keep logic lightweight
|
|
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. Configure matcher
|
|
352
|
+
export const config = {
|
|
353
|
+
matcher: ["/dashboard/:path*"],
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### ❌ DON'T
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// 1. Heavy database queries
|
|
361
|
+
export async function middleware(request: NextRequest) {
|
|
362
|
+
// ❌ No DB queries in middleware
|
|
363
|
+
const user = await prisma.user.findUnique({ where: { id: "..." } })
|
|
364
|
+
return NextResponse.next()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 2. Processing all requests without matcher
|
|
368
|
+
export function middleware(request: NextRequest) {
|
|
369
|
+
// ❌ Performance degradation
|
|
370
|
+
return NextResponse.next()
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Using with 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
|
+
## References
|
|
390
|
+
|
|
391
|
+
- [Next.js Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)
|