tribunal-kit 2.4.6 → 3.0.0
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/.agent/agents/accessibility-reviewer.md +220 -134
- package/.agent/agents/ai-code-reviewer.md +233 -129
- package/.agent/agents/backend-specialist.md +238 -178
- package/.agent/agents/code-archaeologist.md +181 -119
- package/.agent/agents/database-architect.md +207 -164
- package/.agent/agents/debugger.md +218 -151
- package/.agent/agents/dependency-reviewer.md +136 -55
- package/.agent/agents/devops-engineer.md +238 -175
- package/.agent/agents/documentation-writer.md +221 -137
- package/.agent/agents/explorer-agent.md +180 -142
- package/.agent/agents/frontend-reviewer.md +194 -80
- package/.agent/agents/frontend-specialist.md +237 -188
- package/.agent/agents/game-developer.md +52 -184
- package/.agent/agents/logic-reviewer.md +149 -78
- package/.agent/agents/mobile-developer.md +223 -152
- package/.agent/agents/mobile-reviewer.md +195 -79
- package/.agent/agents/orchestrator.md +211 -170
- package/.agent/agents/penetration-tester.md +174 -131
- package/.agent/agents/performance-optimizer.md +203 -139
- package/.agent/agents/performance-reviewer.md +211 -108
- package/.agent/agents/product-manager.md +162 -108
- package/.agent/agents/project-planner.md +162 -142
- package/.agent/agents/qa-automation-engineer.md +242 -138
- package/.agent/agents/security-auditor.md +194 -170
- package/.agent/agents/seo-specialist.md +213 -132
- package/.agent/agents/sql-reviewer.md +194 -73
- package/.agent/agents/supervisor-agent.md +203 -156
- package/.agent/agents/test-coverage-reviewer.md +193 -81
- package/.agent/agents/type-safety-reviewer.md +208 -65
- package/.agent/scripts/__pycache__/auto_preview.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/bundle_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/checklist.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/dependency_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/session_manager.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/skill_integrator.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/swarm_dispatcher.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/verify_all.cpython-311.pyc +0 -0
- package/.agent/skills/agent-organizer/SKILL.md +126 -132
- package/.agent/skills/ai-prompt-injection-defense/SKILL.md +155 -66
- package/.agent/skills/api-patterns/SKILL.md +289 -257
- package/.agent/skills/api-security-auditor/SKILL.md +172 -70
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
- package/.agent/skills/appflow-wireframe/SKILL.md +107 -100
- package/.agent/skills/architecture/SKILL.md +331 -200
- package/.agent/skills/authentication-best-practices/SKILL.md +168 -67
- package/.agent/skills/bash-linux/SKILL.md +154 -215
- package/.agent/skills/brainstorming/SKILL.md +104 -210
- package/.agent/skills/building-native-ui/SKILL.md +169 -70
- package/.agent/skills/clean-code/SKILL.md +360 -206
- package/.agent/skills/config-validator/SKILL.md +141 -165
- package/.agent/skills/csharp-developer/SKILL.md +528 -107
- package/.agent/skills/database-design/SKILL.md +455 -275
- package/.agent/skills/deployment-procedures/SKILL.md +145 -188
- package/.agent/skills/devops-engineer/SKILL.md +332 -134
- package/.agent/skills/devops-incident-responder/SKILL.md +113 -98
- package/.agent/skills/edge-computing/SKILL.md +157 -213
- package/.agent/skills/extract-design-system/SKILL.md +129 -69
- package/.agent/skills/framer-motion-expert/SKILL.md +939 -0
- package/.agent/skills/game-design-expert/SKILL.md +105 -0
- package/.agent/skills/game-engineering-expert/SKILL.md +122 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +124 -215
- package/.agent/skills/github-operations/SKILL.md +314 -354
- package/.agent/skills/gsap-expert/SKILL.md +901 -0
- package/.agent/skills/i18n-localization/SKILL.md +138 -216
- package/.agent/skills/intelligent-routing/SKILL.md +127 -139
- package/.agent/skills/llm-engineering/SKILL.md +357 -258
- package/.agent/skills/local-first/SKILL.md +154 -203
- package/.agent/skills/mcp-builder/SKILL.md +118 -224
- package/.agent/skills/nextjs-react-expert/SKILL.md +783 -203
- package/.agent/skills/nodejs-best-practices/SKILL.md +559 -280
- package/.agent/skills/observability/SKILL.md +330 -285
- package/.agent/skills/parallel-agents/SKILL.md +122 -181
- package/.agent/skills/performance-profiling/SKILL.md +254 -197
- package/.agent/skills/plan-writing/SKILL.md +118 -188
- package/.agent/skills/platform-engineer/SKILL.md +123 -135
- package/.agent/skills/playwright-best-practices/SKILL.md +157 -76
- package/.agent/skills/powershell-windows/SKILL.md +146 -230
- package/.agent/skills/python-pro/SKILL.md +879 -114
- package/.agent/skills/react-specialist/SKILL.md +931 -108
- package/.agent/skills/realtime-patterns/SKILL.md +304 -296
- package/.agent/skills/rust-pro/SKILL.md +701 -240
- package/.agent/skills/seo-fundamentals/SKILL.md +154 -181
- package/.agent/skills/server-management/SKILL.md +190 -212
- package/.agent/skills/shadcn-ui-expert/SKILL.md +201 -68
- package/.agent/skills/sql-pro/SKILL.md +633 -104
- package/.agent/skills/swiftui-expert/SKILL.md +171 -70
- package/.agent/skills/systematic-debugging/SKILL.md +118 -186
- package/.agent/skills/tailwind-patterns/SKILL.md +576 -232
- package/.agent/skills/tdd-workflow/SKILL.md +137 -209
- package/.agent/skills/testing-patterns/SKILL.md +573 -205
- package/.agent/skills/vue-expert/SKILL.md +964 -119
- package/.agent/skills/vulnerability-scanner/SKILL.md +269 -316
- package/.agent/skills/web-accessibility-auditor/SKILL.md +188 -71
- package/.agent/skills/webapp-testing/SKILL.md +145 -236
- package/.agent/workflows/api-tester.md +151 -279
- package/.agent/workflows/audit.md +138 -168
- package/.agent/workflows/brainstorm.md +110 -146
- package/.agent/workflows/changelog.md +112 -144
- package/.agent/workflows/create.md +124 -139
- package/.agent/workflows/debug.md +189 -196
- package/.agent/workflows/deploy.md +189 -153
- package/.agent/workflows/enhance.md +151 -139
- package/.agent/workflows/fix.md +135 -143
- package/.agent/workflows/generate.md +157 -164
- package/.agent/workflows/migrate.md +160 -163
- package/.agent/workflows/orchestrate.md +168 -151
- package/.agent/workflows/performance-benchmarker.md +123 -305
- package/.agent/workflows/plan.md +173 -151
- package/.agent/workflows/preview.md +80 -137
- package/.agent/workflows/refactor.md +183 -153
- package/.agent/workflows/review-ai.md +129 -140
- package/.agent/workflows/review.md +116 -155
- package/.agent/workflows/session.md +94 -154
- package/.agent/workflows/status.md +79 -125
- package/.agent/workflows/strengthen-skills.md +139 -99
- package/.agent/workflows/swarm.md +179 -194
- package/.agent/workflows/test.md +211 -166
- package/.agent/workflows/tribunal-backend.md +113 -111
- package/.agent/workflows/tribunal-database.md +115 -132
- package/.agent/workflows/tribunal-frontend.md +118 -115
- package/.agent/workflows/tribunal-full.md +133 -136
- package/.agent/workflows/tribunal-mobile.md +119 -123
- package/.agent/workflows/tribunal-performance.md +133 -152
- package/.agent/workflows/ui-ux-pro-max.md +143 -171
- package/README.md +11 -15
- package/package.json +1 -1
- package/.agent/skills/dotnet-core-expert/SKILL.md +0 -103
- package/.agent/skills/framer-motion-animations/SKILL.md +0 -74
- package/.agent/skills/game-development/2d-games/SKILL.md +0 -119
- package/.agent/skills/game-development/3d-games/SKILL.md +0 -135
- package/.agent/skills/game-development/SKILL.md +0 -236
- package/.agent/skills/game-development/game-art/SKILL.md +0 -185
- package/.agent/skills/game-development/game-audio/SKILL.md +0 -190
- package/.agent/skills/game-development/game-design/SKILL.md +0 -129
- package/.agent/skills/game-development/mobile-games/SKILL.md +0 -108
- package/.agent/skills/game-development/multiplayer/SKILL.md +0 -132
- package/.agent/skills/game-development/pc-games/SKILL.md +0 -144
- package/.agent/skills/game-development/vr-ar/SKILL.md +0 -123
- package/.agent/skills/game-development/web-games/SKILL.md +0 -150
|
@@ -1,203 +1,783 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nextjs-react-expert
|
|
3
|
-
description: Next.js App Router
|
|
4
|
-
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
-
version:
|
|
6
|
-
last-updated: 2026-03-
|
|
7
|
-
applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
> The fastest code is code that doesn't run.
|
|
13
|
-
> The second fastest is code that runs on the edge and streams the result.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `getServerSideProps` /
|
|
23
|
-
| `
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-react-expert
|
|
3
|
+
description: Next.js 15+ App Router mastery. Server Components, Server Actions, Streaming SSR, Partial Prerendering (PPR), route handlers, middleware, caching/revalidation, generateMetadata, parallel routes, intercepting routes, and AI streaming UI. Use when building Next.js applications, optimizing performance, eliminating waterfalls, or implementing App Router patterns.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
last-updated: 2026-03-30
|
|
7
|
+
applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Next.js 15+ App Router — Pro-Max Patterns
|
|
11
|
+
|
|
12
|
+
> The fastest code is code that doesn't run on the client.
|
|
13
|
+
> The second fastest is code that runs on the edge and streams the result.
|
|
14
|
+
> If you're using `getServerSideProps`, `getStaticProps`, or the Pages Router — you're writing legacy.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Paradigm Shifts (Next.js 14 → 15+)
|
|
19
|
+
|
|
20
|
+
| Legacy (Pages Router / Next 14) | Modern (App Router / Next 15+) |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `getServerSideProps` / `getStaticProps` | **Server Components** fetch directly |
|
|
23
|
+
| Manual `useMemo()` / `useCallback()` | **React Compiler** handles memoization |
|
|
24
|
+
| Client-side form handling | **Server Actions** native mutations |
|
|
25
|
+
| Loading spinners on client | **Streaming UI** + `<Suspense>` boundaries |
|
|
26
|
+
| Static *or* Dynamic pages | **Partial Prerendering (PPR)** — both in one route |
|
|
27
|
+
| `next/router` (`useRouter`) | `next/navigation` (`useRouter`, `usePathname`, `useSearchParams`) |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## App Router File Conventions
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
app/
|
|
35
|
+
├── layout.tsx ← Root layout (wraps ALL routes)
|
|
36
|
+
├── page.tsx ← Home page (/)
|
|
37
|
+
├── loading.tsx ← Automatic <Suspense> fallback for the route
|
|
38
|
+
├── error.tsx ← Error boundary for the route
|
|
39
|
+
├── not-found.tsx ← 404 page
|
|
40
|
+
├── global-error.tsx ← Root error boundary (wraps layout)
|
|
41
|
+
│
|
|
42
|
+
├── dashboard/
|
|
43
|
+
│ ├── layout.tsx ← Dashboard layout (persists across sub-routes)
|
|
44
|
+
│ ├── page.tsx ← /dashboard
|
|
45
|
+
│ ├── loading.tsx ← Dashboard loading state
|
|
46
|
+
│ ├── error.tsx ← Dashboard error boundary
|
|
47
|
+
│ │
|
|
48
|
+
│ ├── settings/
|
|
49
|
+
│ │ └── page.tsx ← /dashboard/settings
|
|
50
|
+
│ │
|
|
51
|
+
│ └── [userId]/ ← Dynamic segment
|
|
52
|
+
│ └── page.tsx ← /dashboard/abc123
|
|
53
|
+
│
|
|
54
|
+
├── api/
|
|
55
|
+
│ └── users/
|
|
56
|
+
│ └── route.ts ← API Route Handler (GET, POST, etc.)
|
|
57
|
+
│
|
|
58
|
+
├── @modal/ ← Parallel route (named slot)
|
|
59
|
+
│ └── login/
|
|
60
|
+
│ └── page.tsx ← Rendered in parallel with main content
|
|
61
|
+
│
|
|
62
|
+
└── (marketing)/ ← Route group (no URL impact — for layout organization)
|
|
63
|
+
├── layout.tsx ← Layout ONLY for marketing pages
|
|
64
|
+
├── about/
|
|
65
|
+
│ └── page.tsx ← /about (NOT /marketing/about)
|
|
66
|
+
└── pricing/
|
|
67
|
+
└── page.tsx ← /pricing
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// ❌ HALLUCINATION TRAP: These Pages Router files DO NOT EXIST in App Router
|
|
72
|
+
// _app.tsx → use app/layout.tsx
|
|
73
|
+
// _document.tsx → use app/layout.tsx with <html> and <body>
|
|
74
|
+
// pages/api/ → use app/api/route.ts
|
|
75
|
+
// getServerSideProps → fetch directly in Server Components
|
|
76
|
+
// getStaticProps → fetch at build time or use generateStaticParams
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Server vs Client Components
|
|
82
|
+
|
|
83
|
+
### The Decision
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Default: Server Component (Zero JS sent to client)
|
|
87
|
+
Switch to Client ('use client') ONLY when:
|
|
88
|
+
✓ Uses browser APIs (window, localStorage, navigator, IntersectionObserver)
|
|
89
|
+
✓ Needs DOM event handlers (onClick, onChange, onSubmit)
|
|
90
|
+
✓ Needs state/effects (useState, useEffect, useRef for DOM)
|
|
91
|
+
✓ Needs Framer Motion, GSAP, or client-side animation libraries
|
|
92
|
+
|
|
93
|
+
Everything else stays on the server.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### The Interleaving Pattern
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
// ✅ CORRECT: Keep the shell on the server, pass server content INTO client
|
|
100
|
+
// app/dashboard/page.tsx (Server Component)
|
|
101
|
+
import { ClientSidebar } from "./ClientSidebar";
|
|
102
|
+
import { ServerStats } from "./ServerStats"; // fetches DB directly
|
|
103
|
+
|
|
104
|
+
export default async function DashboardPage() {
|
|
105
|
+
const stats = await getStats(); // no API call needed — direct DB
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className="flex">
|
|
109
|
+
<ClientSidebar> {/* client: has onClick, state */}
|
|
110
|
+
<ServerStats data={stats} /> {/* server: zero JS, renders HTML */}
|
|
111
|
+
</ClientSidebar>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ❌ WRONG: Making the entire page "use client" because one button needs onClick
|
|
117
|
+
// This ships ALL the JS to the client — defeats the purpose of RSC
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Serialization Boundary
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// Only serializable data can cross the server→client boundary
|
|
124
|
+
// ✅ Can pass: strings, numbers, booleans, arrays, plain objects, Date, Map, Set
|
|
125
|
+
// ❌ Cannot pass: functions, class instances, DOM nodes, Symbols
|
|
126
|
+
|
|
127
|
+
// ❌ BAD: Passing a function from server to client
|
|
128
|
+
<ClientButton onClick={() => deleteItem(id)} /> // functions aren't serializable
|
|
129
|
+
|
|
130
|
+
// ✅ GOOD: Use a Server Action instead
|
|
131
|
+
<ClientButton deleteAction={deleteItemAction} itemId={id} />
|
|
132
|
+
|
|
133
|
+
// In ClientButton:
|
|
134
|
+
"use client";
|
|
135
|
+
function ClientButton({ deleteAction, itemId }) {
|
|
136
|
+
return <button onClick={() => deleteAction(itemId)}>Delete</button>;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Server Actions
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// Server Actions are async functions that run on the server
|
|
146
|
+
// They can be called from client or server components
|
|
147
|
+
|
|
148
|
+
// ✅ Inline Server Action (defined in a Server Component)
|
|
149
|
+
export default function Page() {
|
|
150
|
+
async function createUser(formData: FormData) {
|
|
151
|
+
"use server"; // marks this function as a Server Action
|
|
152
|
+
|
|
153
|
+
const name = formData.get("name") as string;
|
|
154
|
+
const email = formData.get("email") as string;
|
|
155
|
+
|
|
156
|
+
await db.user.create({ data: { name, email } });
|
|
157
|
+
revalidatePath("/users"); // bust cache for /users
|
|
158
|
+
redirect("/users"); // redirect after mutation
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<form action={createUser}>
|
|
163
|
+
<input name="name" required />
|
|
164
|
+
<input name="email" type="email" required />
|
|
165
|
+
<button type="submit">Create</button>
|
|
166
|
+
</form>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Separate Action File
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
// app/actions/user.ts
|
|
175
|
+
"use server";
|
|
176
|
+
|
|
177
|
+
import { revalidatePath } from "next/cache";
|
|
178
|
+
import { redirect } from "next/navigation";
|
|
179
|
+
import { z } from "zod";
|
|
180
|
+
|
|
181
|
+
const UserSchema = z.object({
|
|
182
|
+
name: z.string().min(2),
|
|
183
|
+
email: z.string().email(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export async function createUser(prevState: any, formData: FormData) {
|
|
187
|
+
const parsed = UserSchema.safeParse({
|
|
188
|
+
name: formData.get("name"),
|
|
189
|
+
email: formData.get("email"),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (!parsed.success) {
|
|
193
|
+
return { errors: parsed.error.flatten().fieldErrors };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await db.user.create({ data: parsed.data });
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return { errors: { _form: ["Failed to create user"] } };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
revalidatePath("/users");
|
|
203
|
+
redirect("/users");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ❌ HALLUCINATION TRAP: Server Actions MUST be in a file with "use server"
|
|
207
|
+
// at the top, OR defined inline with "use server" inside the function body.
|
|
208
|
+
// They CANNOT be imported from a regular module.
|
|
209
|
+
|
|
210
|
+
// ❌ HALLUCINATION TRAP: Always validate input in Server Actions.
|
|
211
|
+
// FormData comes from the client — it's user input. Never trust it.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Data Fetching & Caching
|
|
217
|
+
|
|
218
|
+
### Fetching in Server Components
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// Server Components can fetch data directly — no useEffect, no API route
|
|
222
|
+
export default async function UsersPage() {
|
|
223
|
+
const users = await db.user.findMany(); // direct DB call
|
|
224
|
+
// OR
|
|
225
|
+
const res = await fetch("https://api.example.com/users", {
|
|
226
|
+
next: { revalidate: 3600 }, // ISR: revalidate every hour
|
|
227
|
+
});
|
|
228
|
+
const users = await res.json();
|
|
229
|
+
|
|
230
|
+
return <UserList users={users} />;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Caching Strategies
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
// 1. Static (cached forever until revalidated)
|
|
238
|
+
const data = await fetch(url); // default: cached
|
|
239
|
+
|
|
240
|
+
// 2. ISR (Incremental Static Regeneration)
|
|
241
|
+
const data = await fetch(url, {
|
|
242
|
+
next: { revalidate: 60 }, // revalidate every 60 seconds
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 3. Dynamic (never cached)
|
|
246
|
+
const data = await fetch(url, { cache: "no-store" });
|
|
247
|
+
|
|
248
|
+
// 4. On-demand revalidation (Server Actions / Webhooks)
|
|
249
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
250
|
+
|
|
251
|
+
// Revalidate a specific path
|
|
252
|
+
revalidatePath("/dashboard");
|
|
253
|
+
|
|
254
|
+
// Revalidate by tag
|
|
255
|
+
const data = await fetch(url, { next: { tags: ["users"] } });
|
|
256
|
+
// Later, in a Server Action:
|
|
257
|
+
revalidateTag("users"); // busts all fetches tagged "users"
|
|
258
|
+
|
|
259
|
+
// ❌ HALLUCINATION TRAP: Next.js 15 changed the default caching behavior
|
|
260
|
+
// In Next.js 14: fetch was cached by default
|
|
261
|
+
// In Next.js 15: fetch is NOT cached by default (dynamic by default)
|
|
262
|
+
// You must explicitly opt into caching with next.revalidate or cache: "force-cache"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### `unstable_cache` for Non-Fetch Data
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { unstable_cache } from "next/cache";
|
|
269
|
+
|
|
270
|
+
// Cache database queries that don't use fetch()
|
|
271
|
+
const getCachedUser = unstable_cache(
|
|
272
|
+
async (userId: string) => {
|
|
273
|
+
return await db.user.findUnique({ where: { id: userId } });
|
|
274
|
+
},
|
|
275
|
+
["user-by-id"], // cache key parts
|
|
276
|
+
{
|
|
277
|
+
revalidate: 3600, // 1 hour
|
|
278
|
+
tags: ["user"], // for on-demand revalidation
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Usage:
|
|
283
|
+
const user = await getCachedUser("abc123");
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Waterfall Elimination
|
|
289
|
+
|
|
290
|
+
### The Problem
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// ❌ CRITICAL WATERFALL: Each await blocks the next
|
|
294
|
+
async function Dashboard() {
|
|
295
|
+
const user = await getUser(); // 200ms
|
|
296
|
+
const posts = await getPosts(); // 200ms (waits for user)
|
|
297
|
+
const analytics = await getAnalytics(); // 200ms (waits for posts)
|
|
298
|
+
// Total: 600ms sequential
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Fix 1: Parallel Fetching
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
// ✅ All three start simultaneously
|
|
306
|
+
async function Dashboard() {
|
|
307
|
+
const [user, posts, analytics] = await Promise.all([
|
|
308
|
+
getUser(),
|
|
309
|
+
getPosts(),
|
|
310
|
+
getAnalytics(),
|
|
311
|
+
]);
|
|
312
|
+
// Total: ~200ms (time of slowest call)
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Fix 2: Streaming with Suspense (Pro-Max)
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// 🚀 Show fast content immediately, stream slow content later
|
|
320
|
+
export default function Dashboard() {
|
|
321
|
+
return (
|
|
322
|
+
<main>
|
|
323
|
+
<FastHeader /> {/* renders instantly — static */}
|
|
324
|
+
|
|
325
|
+
<Suspense fallback={<StatsSkeleton />}>
|
|
326
|
+
<SlowStatsPanel /> {/* streams when DB resolves */}
|
|
327
|
+
</Suspense>
|
|
328
|
+
|
|
329
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
330
|
+
<VerySlowChart /> {/* streams when API resolves */}
|
|
331
|
+
</Suspense>
|
|
332
|
+
</main>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Each Suspense boundary independently streams its content
|
|
337
|
+
// The user sees the page progressively — not a blank screen
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Partial Prerendering (PPR)
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
// next.config.ts
|
|
346
|
+
export default {
|
|
347
|
+
experimental: {
|
|
348
|
+
ppr: true, // Enable Partial Prerendering
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// PPR = Static shell (edge-cached) + Dynamic holes (streamed)
|
|
353
|
+
// The static parts are served from CDN at edge speed
|
|
354
|
+
// Dynamic parts stream in from the server
|
|
355
|
+
|
|
356
|
+
export default function ProductPage({ params }: { params: { id: string } }) {
|
|
357
|
+
return (
|
|
358
|
+
<main>
|
|
359
|
+
{/* 🟢 STATIC — pre-rendered at build time, served from CDN */}
|
|
360
|
+
<Header />
|
|
361
|
+
<ProductDetails id={params.id} />
|
|
362
|
+
|
|
363
|
+
{/* 🔴 DYNAMIC — streamed on request (requires cookies/headers) */}
|
|
364
|
+
<Suspense fallback={<CartSkeleton />}>
|
|
365
|
+
<PersonalizedCart /> {/* reads cookies() — dynamic */}
|
|
366
|
+
</Suspense>
|
|
367
|
+
|
|
368
|
+
<Suspense fallback={<ReviewsSkeleton />}>
|
|
369
|
+
<LiveReviews /> {/* real-time data — dynamic */}
|
|
370
|
+
</Suspense>
|
|
371
|
+
</main>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ❌ HALLUCINATION TRAP: Using cookies(), headers(), or searchParams at the
|
|
376
|
+
// top level of a component tree forces the ENTIRE route to be dynamic.
|
|
377
|
+
// Isolate dynamic data inside Suspense boundaries for PPR to work.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Metadata & SEO
|
|
383
|
+
|
|
384
|
+
### `generateMetadata` (Dynamic)
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { Metadata } from "next";
|
|
388
|
+
|
|
389
|
+
// Static metadata
|
|
390
|
+
export const metadata: Metadata = {
|
|
391
|
+
title: "My App",
|
|
392
|
+
description: "The best app ever",
|
|
393
|
+
openGraph: { title: "My App", description: "...", images: ["/og.png"] },
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Dynamic metadata (based on params/data)
|
|
397
|
+
export async function generateMetadata({
|
|
398
|
+
params,
|
|
399
|
+
}: {
|
|
400
|
+
params: { slug: string };
|
|
401
|
+
}): Promise<Metadata> {
|
|
402
|
+
const post = await getPost(params.slug);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
title: post.title,
|
|
406
|
+
description: post.excerpt,
|
|
407
|
+
openGraph: {
|
|
408
|
+
title: post.title,
|
|
409
|
+
description: post.excerpt,
|
|
410
|
+
images: [post.coverImage],
|
|
411
|
+
},
|
|
412
|
+
twitter: {
|
|
413
|
+
card: "summary_large_image",
|
|
414
|
+
title: post.title,
|
|
415
|
+
},
|
|
416
|
+
alternates: {
|
|
417
|
+
canonical: `https://example.com/blog/${params.slug}`,
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ❌ HALLUCINATION TRAP: generateMetadata is an async function exported
|
|
423
|
+
// from page.tsx or layout.tsx — NOT a React component.
|
|
424
|
+
// It runs on the server during rendering.
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### `generateStaticParams` (Static Generation)
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
// Pre-generate pages at build time (SSG)
|
|
431
|
+
export async function generateStaticParams() {
|
|
432
|
+
const posts = await getAllPosts();
|
|
433
|
+
|
|
434
|
+
return posts.map((post) => ({
|
|
435
|
+
slug: post.slug, // matches [slug] dynamic segment
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Combined with dynamicParams:
|
|
440
|
+
export const dynamicParams = false; // 404 for unknown slugs
|
|
441
|
+
// OR
|
|
442
|
+
export const dynamicParams = true; // generate on-demand (default)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Route Handlers (API Routes)
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
// app/api/users/route.ts
|
|
451
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
452
|
+
|
|
453
|
+
export async function GET(request: NextRequest) {
|
|
454
|
+
const searchParams = request.nextUrl.searchParams;
|
|
455
|
+
const page = searchParams.get("page") ?? "1";
|
|
456
|
+
|
|
457
|
+
const users = await db.user.findMany({
|
|
458
|
+
skip: (parseInt(page) - 1) * 20,
|
|
459
|
+
take: 20,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return NextResponse.json(users);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export async function POST(request: NextRequest) {
|
|
466
|
+
const body = await request.json();
|
|
467
|
+
|
|
468
|
+
// Always validate input
|
|
469
|
+
const parsed = UserSchema.safeParse(body);
|
|
470
|
+
if (!parsed.success) {
|
|
471
|
+
return NextResponse.json(
|
|
472
|
+
{ error: parsed.error.flatten() },
|
|
473
|
+
{ status: 400 }
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const user = await db.user.create({ data: parsed.data });
|
|
478
|
+
return NextResponse.json(user, { status: 201 });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Dynamic route: app/api/users/[id]/route.ts
|
|
482
|
+
export async function GET(
|
|
483
|
+
request: NextRequest,
|
|
484
|
+
{ params }: { params: { id: string } }
|
|
485
|
+
) {
|
|
486
|
+
const user = await db.user.findUnique({ where: { id: params.id } });
|
|
487
|
+
if (!user) {
|
|
488
|
+
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
489
|
+
}
|
|
490
|
+
return NextResponse.json(user);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ❌ HALLUCINATION TRAP: Route handlers use named exports (GET, POST, PUT, DELETE)
|
|
494
|
+
// NOT default export. NOT export function handler().
|
|
495
|
+
// ❌ HALLUCINATION TRAP: Route handlers are in route.ts, NOT in page.tsx
|
|
496
|
+
// A directory cannot have both page.tsx and route.ts
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Middleware
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
// middleware.ts (in project ROOT, not inside app/)
|
|
505
|
+
import { NextResponse } from "next/server";
|
|
506
|
+
import type { NextRequest } from "next/server";
|
|
507
|
+
|
|
508
|
+
export function middleware(request: NextRequest) {
|
|
509
|
+
const token = request.cookies.get("session")?.value;
|
|
510
|
+
const { pathname } = request.nextUrl;
|
|
511
|
+
|
|
512
|
+
// Redirect unauthenticated users
|
|
513
|
+
if (pathname.startsWith("/dashboard") && !token) {
|
|
514
|
+
return NextResponse.redirect(new URL("/login", request.url));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Add custom headers
|
|
518
|
+
const response = NextResponse.next();
|
|
519
|
+
response.headers.set("x-pathname", pathname);
|
|
520
|
+
|
|
521
|
+
return response;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Matcher: only run middleware on specific routes
|
|
525
|
+
export const config = {
|
|
526
|
+
matcher: [
|
|
527
|
+
"/dashboard/:path*",
|
|
528
|
+
"/api/:path*",
|
|
529
|
+
// Skip static files and Next.js internals
|
|
530
|
+
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
531
|
+
],
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// ❌ HALLUCINATION TRAP: middleware.ts must be at the project ROOT
|
|
535
|
+
// (same level as app/ directory), NOT inside app/
|
|
536
|
+
// ❌ HALLUCINATION TRAP: Middleware runs on the Edge Runtime
|
|
537
|
+
// You CANNOT use Node.js APIs (fs, crypto.createHash, etc.)
|
|
538
|
+
// Use Web APIs (crypto.subtle, fetch, Response, Headers)
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Parallel & Intercepting Routes
|
|
544
|
+
|
|
545
|
+
### Parallel Routes (Named Slots)
|
|
546
|
+
|
|
547
|
+
```
|
|
548
|
+
app/
|
|
549
|
+
├── layout.tsx
|
|
550
|
+
├── page.tsx
|
|
551
|
+
├── @analytics/
|
|
552
|
+
│ └── page.tsx ← Rendered in parallel with main page
|
|
553
|
+
└── @notifications/
|
|
554
|
+
└── page.tsx ← Also rendered in parallel
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```tsx
|
|
558
|
+
// app/layout.tsx
|
|
559
|
+
export default function Layout({
|
|
560
|
+
children,
|
|
561
|
+
analytics,
|
|
562
|
+
notifications,
|
|
563
|
+
}: {
|
|
564
|
+
children: React.ReactNode;
|
|
565
|
+
analytics: React.ReactNode;
|
|
566
|
+
notifications: React.ReactNode;
|
|
567
|
+
}) {
|
|
568
|
+
return (
|
|
569
|
+
<div>
|
|
570
|
+
<main>{children}</main>
|
|
571
|
+
<aside>{analytics}</aside>
|
|
572
|
+
<div>{notifications}</div>
|
|
573
|
+
</div>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Intercepting Routes (Modal Pattern)
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
app/
|
|
582
|
+
├── feed/
|
|
583
|
+
│ └── page.tsx ← /feed (shows feed)
|
|
584
|
+
├── photo/[id]/
|
|
585
|
+
│ └── page.tsx ← /photo/123 (full photo page)
|
|
586
|
+
└── @modal/
|
|
587
|
+
└── (..)photo/[id]/
|
|
588
|
+
└── page.tsx ← Intercepts /photo/123 when navigating from /feed
|
|
589
|
+
Shows as modal overlay instead of full page
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
```
|
|
593
|
+
Intercepting conventions:
|
|
594
|
+
(.) — same level
|
|
595
|
+
(..) — one level up
|
|
596
|
+
(..)(..) — two levels up
|
|
597
|
+
(...) — from root
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## AI & Streaming UI
|
|
603
|
+
|
|
604
|
+
```tsx
|
|
605
|
+
import { openai } from "@ai-sdk/openai";
|
|
606
|
+
import { streamText } from "ai";
|
|
607
|
+
|
|
608
|
+
// app/api/chat/route.ts
|
|
609
|
+
export async function POST(req: NextRequest) {
|
|
610
|
+
const { messages } = await req.json();
|
|
611
|
+
|
|
612
|
+
const result = streamText({
|
|
613
|
+
model: openai("gpt-4o"),
|
|
614
|
+
messages,
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
return result.toDataStreamResponse();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Client component:
|
|
621
|
+
"use client";
|
|
622
|
+
import { useChat } from "@ai-sdk/react";
|
|
623
|
+
|
|
624
|
+
function ChatUI() {
|
|
625
|
+
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<div>
|
|
629
|
+
{messages.map((m) => (
|
|
630
|
+
<div key={m.id} className={m.role === "user" ? "user" : "ai"}>
|
|
631
|
+
{m.content}
|
|
632
|
+
</div>
|
|
633
|
+
))}
|
|
634
|
+
<form onSubmit={handleSubmit}>
|
|
635
|
+
<input value={input} onChange={handleInputChange} />
|
|
636
|
+
<button disabled={isLoading}>Send</button>
|
|
637
|
+
</form>
|
|
638
|
+
</div>
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ❌ HALLUCINATION TRAP: Import from "@ai-sdk/react", NOT "ai/react"
|
|
643
|
+
// The package was restructured in Vercel AI SDK 4+
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## Bundle Optimization
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
// 1. Push "use client" as FAR DOWN as possible
|
|
652
|
+
// ❌ BAD: "use client" on layout.tsx (ships entire layout as JS)
|
|
653
|
+
// ✅ GOOD: "use client" only on the interactive widget component
|
|
654
|
+
|
|
655
|
+
// 2. Dynamic imports for heavy client deps
|
|
656
|
+
import dynamic from "next/dynamic";
|
|
657
|
+
|
|
658
|
+
const HeavyChart = dynamic(() => import("./HeavyChart"), {
|
|
659
|
+
ssr: false, // skip server rendering
|
|
660
|
+
loading: () => <ChartSkeleton />,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// 3. next/image for automatic optimization
|
|
664
|
+
import Image from "next/image";
|
|
665
|
+
|
|
666
|
+
<Image
|
|
667
|
+
src="/hero.jpg"
|
|
668
|
+
alt="Hero image"
|
|
669
|
+
width={1200}
|
|
670
|
+
height={630}
|
|
671
|
+
priority // preload for LCP images
|
|
672
|
+
placeholder="blur"
|
|
673
|
+
blurDataURL={blurUrl}
|
|
674
|
+
/>
|
|
675
|
+
|
|
676
|
+
// 4. next/font for zero-layout-shift fonts
|
|
677
|
+
import { Inter } from "next/font/google";
|
|
678
|
+
|
|
679
|
+
const inter = Inter({
|
|
680
|
+
subsets: ["latin"],
|
|
681
|
+
display: "swap",
|
|
682
|
+
variable: "--font-inter",
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// In layout.tsx:
|
|
686
|
+
<html className={inter.variable}>
|
|
687
|
+
<body>{children}</body>
|
|
688
|
+
</html>
|
|
689
|
+
|
|
690
|
+
// ❌ HALLUCINATION TRAP: next/font automatically self-hosts fonts
|
|
691
|
+
// Do NOT add Google Fonts <link> tags in <head> — they cause CLS
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Key Anti-Patterns
|
|
697
|
+
|
|
698
|
+
| Pattern | Problem | Fix |
|
|
699
|
+
|---|---|---|
|
|
700
|
+
| `getServerSideProps` in App Router | Pages Router API — doesn't exist | Fetch directly in Server Components |
|
|
701
|
+
| `"use client"` on layout/page | Ships massive JS bundle | Push `"use client"` to leaf components |
|
|
702
|
+
| `useEffect(() => fetch(...))` | Client waterfall, no cache, CLS | Server Component or React Query |
|
|
703
|
+
| Sequential `await` calls | Network waterfall | `Promise.all()` or `<Suspense>` |
|
|
704
|
+
| `cookies()`/`headers()` at top level | Disables PPR for entire route | Isolate inside `<Suspense>` boundaries |
|
|
705
|
+
| `next/router` (Pages Router) | Wrong import | Use `next/navigation` |
|
|
706
|
+
| Missing `loading.tsx` | Blank screen during navigation | Add loading.tsx or Suspense |
|
|
707
|
+
| Raw `<img>` and `<a>` tags | No optimization | Use `next/image` and `next/link` |
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Output Format
|
|
712
|
+
|
|
713
|
+
When this skill produces or reviews code, structure your output as follows:
|
|
714
|
+
|
|
715
|
+
```
|
|
716
|
+
━━━ Next.js Expert Report ━━━━━━━━━━━━━━━━━━━━━━━━
|
|
717
|
+
Skill: Next.js React Expert
|
|
718
|
+
Next.js Ver: 15+
|
|
719
|
+
Scope: [N files · N routes]
|
|
720
|
+
─────────────────────────────────────────────────
|
|
721
|
+
✅ Passed: [checks that passed, or "All clean"]
|
|
722
|
+
⚠️ Warnings: [non-blocking issues, or "None"]
|
|
723
|
+
❌ Blocked: [blocking issues requiring fix, or "None"]
|
|
724
|
+
─────────────────────────────────────────────────
|
|
725
|
+
VBC status: PENDING → VERIFIED
|
|
726
|
+
Evidence: [test output / lint pass / compile success]
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**VBC (Verification-Before-Completion) is mandatory.**
|
|
730
|
+
Do not mark status as VERIFIED until concrete terminal evidence is provided.
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## 🤖 LLM-Specific Traps
|
|
735
|
+
|
|
736
|
+
AI coding assistants often fall into specific bad habits when generating Next.js code. These are strictly forbidden:
|
|
737
|
+
|
|
738
|
+
1. **Pages Router in App Router:** Never generate `getServerSideProps`, `getStaticProps`, `getInitialProps`, `_app.tsx`, `_document.tsx`, or `pages/api/` in an App Router project.
|
|
739
|
+
2. **`"use client"` Everywhere:** Marking layouts, pages, or entire feature modules as client components defeats RSC. Push the `"use client"` boundary as deep as possible.
|
|
740
|
+
3. **`next/router` Import:** The Pages Router `useRouter` is `next/router`. App Router uses `next/navigation`. Using the wrong one causes runtime errors.
|
|
741
|
+
4. **Missing Input Validation in Server Actions:** Server Actions receive raw `FormData` from the client. Always validate with Zod or similar before touching the database.
|
|
742
|
+
5. **`useEffect` for Data Fetching:** Server Components can fetch directly. Client components should use React Query or SWR. `useEffect` fetch has no caching, no deduplication, no error boundaries.
|
|
743
|
+
6. **Forgetting Next.js 15 Cache Changes:** In Next.js 15, `fetch()` is NOT cached by default (changed from 14). You must explicitly opt into caching.
|
|
744
|
+
7. **Google Fonts `<link>` Tags:** Never add external font `<link>` tags. Use `next/font` for zero-CLS, self-hosted fonts.
|
|
745
|
+
8. **Route Handler Default Exports:** Route handlers use named exports (`GET`, `POST`, `DELETE`), not `export default function handler`.
|
|
746
|
+
9. **Middleware Inside `app/`:** `middleware.ts` must be at the project root, not inside the `app/` directory.
|
|
747
|
+
10. **`"ai/react"` Import Path:** The Vercel AI SDK restructured its exports. Use `@ai-sdk/react` for hooks and `ai` for core.
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## 🏛️ Tribunal Integration (Anti-Hallucination)
|
|
752
|
+
|
|
753
|
+
**Slash command: `/tribunal-frontend`**
|
|
754
|
+
**Active reviewers: `logic` · `security` · `frontend` · `type-safety`**
|
|
755
|
+
|
|
756
|
+
### ❌ Forbidden AI Tropes
|
|
757
|
+
|
|
758
|
+
1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `// VERIFY: [reason]`.
|
|
759
|
+
2. **Silent Degradation:** Catching and suppressing errors without logging or displaying error boundaries.
|
|
760
|
+
3. **Context Amnesia:** Forgetting whether the project uses Pages Router or App Router.
|
|
761
|
+
4. **Generic Design:** Do not default to black/white Vercel aesthetics unless instructed.
|
|
762
|
+
|
|
763
|
+
### ✅ Pre-Flight Self-Audit
|
|
764
|
+
|
|
765
|
+
Review these questions before confirming output:
|
|
766
|
+
```
|
|
767
|
+
✅ Did I maximize Server Component usage and isolate "use client"?
|
|
768
|
+
✅ Are there sequential awaits creating a waterfall? Did I use Promise.all or Suspense?
|
|
769
|
+
✅ Did I validate all Server Action inputs with Zod?
|
|
770
|
+
✅ Did I use next/image and next/link (not raw <img> and <a>)?
|
|
771
|
+
✅ Did I implement loading.tsx and error.tsx for route segments?
|
|
772
|
+
✅ Did I use next/font (not external font <link> tags)?
|
|
773
|
+
✅ Did I use next/navigation (not next/router)?
|
|
774
|
+
✅ Are dynamic data reads (cookies, headers) inside Suspense for PPR?
|
|
775
|
+
✅ Did I add generateMetadata for SEO?
|
|
776
|
+
✅ Is middleware.ts at the project root (not inside app/)?
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### 🛑 Verification-Before-Completion (VBC) Protocol
|
|
780
|
+
|
|
781
|
+
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
|
|
782
|
+
- ❌ **Forbidden:** Assuming a Next.js route "works" because the dev server shows no errors.
|
|
783
|
+
- ✅ **Required:** You are explicitly forbidden from completing your task without providing **concrete evidence** (successful `next build`, passing tests, or equivalent proof) that the code compiles and runs correctly.
|