@minhduydev/mdpi 0.4.1 → 0.5.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/dist/index.js +1 -1
- package/dist/template/.pi/VERSION +1 -1
- package/dist/template/.pi/extensions/templates-injector.ts +35 -7
- package/dist/template/.pi/prompts/INDEX.md +3 -9
- package/dist/template/.pi/skills/INDEX.md +39 -8
- package/dist/template/.pi/skills/dcp-hygiene/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-advanced.md +88 -15
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-core.md +148 -13
- package/dist/template/.pi/skills/frontend-design/references/shadcn/setup.md +127 -20
- package/dist/template/.pi/skills/nextjs-app-router/SKILL.md +334 -0
- package/dist/template/.pi/skills/nextjs-cache/SKILL.md +262 -0
- package/dist/template/.pi/skills/react-best-practices/SKILL.md +79 -1
- package/dist/template/.pi/skills/react-compiler/SKILL.md +237 -0
- package/dist/template/.pi/skills/react-hook-form/SKILL.md +374 -0
- package/dist/template/.pi/skills/react-server-actions/SKILL.md +299 -0
- package/dist/template/.pi/skills/shadcn-ui/SKILL.md +404 -0
- package/dist/template/.pi/skills/tanstack-query/SKILL.md +330 -0
- package/dist/template/.pi/skills/v0/SKILL.md +264 -0
- package/dist/template/.pi/skills/zustand/SKILL.md +333 -0
- package/package.json +1 -1
- package/dist/template/.pi/prompts/loop-check.md +0 -87
- package/dist/template/.pi/prompts/loop-init.md +0 -157
- package/dist/template/.pi/prompts/loop-review.md +0 -90
- package/dist/template/.pi/skills/loop-audit/SKILL.md +0 -141
- package/dist/template/.pi/skills/loop-cost/SKILL.md +0 -130
- package/dist/template/.pi/skills/loop-engineering/SKILL.md +0 -175
- package/dist/template/.pi/templates/loop-github-action.yml +0 -162
- package/dist/template/.pi/templates/loop-orchestrator.sh +0 -514
- package/dist/template/.pi/templates/loop-orchestrator.test.ts +0 -332
- package/dist/template/.pi/templates/loop-orchestrator.ts +0 -936
- package/dist/template/.pi/templates/loop-state.json +0 -24
- package/dist/template/.pi/templates/loop-state.md +0 -98
- package/dist/template/.pi/templates/loop-vision.md +0 -110
|
@@ -1,56 +1,161 @@
|
|
|
1
1
|
# shadcn/ui Setup
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
CLI v4.x (current: shadcn@4.11.0) — Copy-paste components, Radix UI / Base UI + Tailwind v4.
|
|
4
4
|
|
|
5
|
-
## New Project
|
|
5
|
+
## Create a New Project
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx shadcn create
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Interactive setup:
|
|
12
|
-
- Visual style
|
|
13
|
-
- Component library
|
|
14
|
-
- Icon library (
|
|
15
|
-
- Next.js 16 support
|
|
12
|
+
- **Visual style**: Vega, Nova, Maia, Lyra, Mira
|
|
13
|
+
- **Component library**: Radix UI or Base UI
|
|
14
|
+
- **Icon library** (Lucide, Phosphor, etc.)
|
|
15
|
+
- **Next.js 16**, Vite, Laravel, React Router, Astro, TanStack Start support
|
|
16
|
+
- **Template scaffolding** via `npx shadcn init --template`
|
|
16
17
|
|
|
17
18
|
## Existing Project
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
|
-
npx shadcn@latest init
|
|
21
|
+
npx shadcn@latest init --preset <code>
|
|
21
22
|
npx shadcn@latest add button card dialog
|
|
22
23
|
npx shadcn@latest add --all
|
|
24
|
+
npx shadcn@latest add <username>/<repo>/<item> # GitHub Registry
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
Components install to `components/ui/`.
|
|
26
28
|
|
|
29
|
+
## Key CLI v4 Commands
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Inspection
|
|
33
|
+
npx shadcn info # Full project config (for agent context)
|
|
34
|
+
npx shadcn info --json # JSON output for programmatic use
|
|
35
|
+
npx shadcn docs <component> # Docs, code, examples from CLI
|
|
36
|
+
|
|
37
|
+
# Preview changes before writing
|
|
38
|
+
npx shadcn add button --dry-run # Show what will be installed
|
|
39
|
+
npx shadcn add button --diff # Show diff against current
|
|
40
|
+
npx shadcn add button --view # Open component in browser
|
|
41
|
+
|
|
42
|
+
# Project setup flags
|
|
43
|
+
npx shadcn init --preset a1Dg5eFl # Pack entire design system into short code
|
|
44
|
+
npx shadcn init --template # Scaffold project templates
|
|
45
|
+
npx shadcn init --monorepo # Monorepo setup
|
|
46
|
+
npx shadcn init --base radix # Choose primitives (radix | base)
|
|
47
|
+
|
|
48
|
+
# Skills (AI Agent)
|
|
49
|
+
npx skills add shadcn/ui # Install agent prompt file
|
|
50
|
+
npx shadcn skills generate # Regenerate after adding components
|
|
51
|
+
npx shadcn skills update # Update monthly
|
|
52
|
+
```
|
|
53
|
+
|
|
27
54
|
## Visual Styles
|
|
28
55
|
|
|
29
|
-
| Style |
|
|
30
|
-
|
|
31
|
-
| **Vega** | Classic shadcn look |
|
|
32
|
-
| **Nova** | Reduced padding, compact |
|
|
33
|
-
| **Maia** | Soft, rounded, generous |
|
|
34
|
-
| **Lyra** | Boxy, sharp,
|
|
35
|
-
| **Mira** | Compact, dense interfaces |
|
|
56
|
+
| Style | Characteristics |
|
|
57
|
+
|-------|----------------|
|
|
58
|
+
| **Vega** | Classic shadcn look — balanced, versatile |
|
|
59
|
+
| **Nova** | Reduced padding, compact, space-efficient |
|
|
60
|
+
| **Maia** | Soft, rounded corners, generous spacing |
|
|
61
|
+
| **Lyra** | Boxy, sharp corners, monospace fonts |
|
|
62
|
+
| **Mira** | Compact, dense, data-heavy interfaces |
|
|
63
|
+
|
|
64
|
+
Styles rewrite component code, not just CSS — fonts, spacing, structure, primitives all adapt.
|
|
65
|
+
|
|
66
|
+
## Presets
|
|
67
|
+
|
|
68
|
+
Presets pack the entire design system into a reproducible short code:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Export your preset
|
|
72
|
+
npx shadcn preset export
|
|
73
|
+
|
|
74
|
+
# Init a project with a preset
|
|
75
|
+
npx shadcn init --preset a1Dg5eFl
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A preset includes: colors, theme, icons, fonts, radius, spacing — everything in one code.
|
|
36
79
|
|
|
37
80
|
## Component Libraries
|
|
38
81
|
|
|
39
|
-
- **Radix UI** (default): Full accessibility, React-only
|
|
40
|
-
- **Base UI**: MUI unstyled components
|
|
82
|
+
- **Radix UI** (default): Full accessibility, React-only, battle-tested
|
|
83
|
+
- **Base UI**: MUI unstyled components, headless
|
|
84
|
+
|
|
85
|
+
Select during `npx shadcn create` or with `--base` flag. CLI auto-detects your library when pulling components.
|
|
86
|
+
|
|
87
|
+
## GitHub Registries (June 2026)
|
|
88
|
+
|
|
89
|
+
Any public GitHub repo with a `registry.json` can be a component registry:
|
|
41
90
|
|
|
42
|
-
|
|
91
|
+
```bash
|
|
92
|
+
npx shadcn@latest add <username>/<repo>/<item>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Distribute: components, hooks, utilities, design tokens, feature kits, project conventions, CI workflows, templates. No build step — CLI reads `registry.json` directly.
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
// registry.json (in any GitHub repo)
|
|
99
|
+
{
|
|
100
|
+
"name": "my-components",
|
|
101
|
+
"items": [
|
|
102
|
+
{
|
|
103
|
+
"name": "data-table",
|
|
104
|
+
"type": "registry:component",
|
|
105
|
+
"files": [{ "path": "components/data-table.tsx" }]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## shadcn/skills — AI Agent Bridge
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx skills add shadcn/ui
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Creates `.shadcn/skills.md` — a machine-readable file that gives AI coding agents project-aware context:
|
|
118
|
+
|
|
119
|
+
- **Project context** — framework, aliases, installed components, icon library, base library (via `shadcn info --json`)
|
|
120
|
+
- **CLI command reference** — all flags, smart merge, presets, templates
|
|
121
|
+
- **Theming guidance** — CSS vars, OKLCH, dark mode, Tailwind v3/v4
|
|
122
|
+
- **Registry authoring** — `registry.json` format
|
|
123
|
+
- **Pattern enforcement** — FieldGroup for forms, ToggleGroup for options, semantic colors
|
|
124
|
+
|
|
125
|
+
**Measured impact**: API errors 34% → 3%, correct variants 61% → 98%, first-try success 45% → 89%.
|
|
126
|
+
|
|
127
|
+
**Critical workflows**:
|
|
128
|
+
- Re-generate after adding components: `npx shadcn skills generate`
|
|
129
|
+
- Update monthly: `npx shadcn skills update`
|
|
130
|
+
- Reference file path (don't paste into context — 4K cutoff)
|
|
43
131
|
|
|
44
132
|
## MCP Server Support
|
|
45
133
|
|
|
46
|
-
|
|
47
|
-
|
|
134
|
+
Connect AI editors to your registry:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"shadcn": {
|
|
140
|
+
"command": "npx",
|
|
141
|
+
"args": ["-y", "shadcn@canary", "registry:mcp"],
|
|
142
|
+
"env": {
|
|
143
|
+
"REGISTRY_URL": "https://your-registry.vercel.app/r/registry.json"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Also: OpenCode MCP (v3.6.3), Codex MCP (v3.4.0).
|
|
48
151
|
|
|
49
152
|
## Registry System
|
|
50
153
|
|
|
51
154
|
- Registry Directory: https://ui.shadcn.com/docs/directory
|
|
52
155
|
- Custom registries via `components.json`
|
|
53
156
|
- Namespaced components
|
|
157
|
+
- `registry:base` — distribute entire design systems
|
|
158
|
+
- `registry:font` — distribute font packages
|
|
54
159
|
|
|
55
160
|
## Component List
|
|
56
161
|
|
|
@@ -66,4 +171,6 @@ Select during `npx shadcn create` or in `components.json`.
|
|
|
66
171
|
|
|
67
172
|
**Overlay**: Dropdown Menu, Context Menu, Popover, Collapsible
|
|
68
173
|
|
|
69
|
-
**
|
|
174
|
+
**2026 New**: Spinner, Kbd, KbdGroup, Button Group, Input Group, Field, Item, Empty, Input OTP
|
|
175
|
+
|
|
176
|
+
**Other**: Toggle, Toggle Group
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-app-router
|
|
3
|
+
description: Use when building or refactoring Next.js App Router pages. Covers file conventions, layouts vs templates, parallel/intercepting routes, route groups, async params, streaming, RSC boundaries. MUST load before any App Router architecture work.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js App Router Patterns (Next.js 15+)
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Building new pages in Next.js App Router
|
|
11
|
+
- Understanding or refactoring App Router file conventions
|
|
12
|
+
- Designing route architecture (parallel routes, intercepting, groups)
|
|
13
|
+
- Setting up layouts, error boundaries, and loading states
|
|
14
|
+
- Making decisions about Server vs Client Components
|
|
15
|
+
|
|
16
|
+
## When NOT to Use
|
|
17
|
+
|
|
18
|
+
- Pages Router projects (`pages/` directory — use `react-best-practices`)
|
|
19
|
+
- Non-Next.js React projects (Vite, Remix, React Router)
|
|
20
|
+
- Pure API routes (not page structure)
|
|
21
|
+
|
|
22
|
+
## File Conventions Reference
|
|
23
|
+
|
|
24
|
+
| File | Purpose | Runs |
|
|
25
|
+
|------|---------|------|
|
|
26
|
+
| `page.tsx` | Route's unique UI | Server (default) |
|
|
27
|
+
| `layout.tsx` | Shared UI that persists across navigations | Server (default) |
|
|
28
|
+
| `template.tsx` | Shared UI that remounts on navigation | Server (default) |
|
|
29
|
+
| `loading.tsx` | Suspense fallback while page loads | Server (default) |
|
|
30
|
+
| `error.tsx` | Error boundary for the segment | Client |
|
|
31
|
+
| `not-found.tsx` | 404 UI for the segment | Server (default) |
|
|
32
|
+
| `default.tsx` | Fallback for parallel routes | Server (default) |
|
|
33
|
+
| `route.tsx` | API endpoint for the segment | Server |
|
|
34
|
+
|
|
35
|
+
## Layout vs Template
|
|
36
|
+
|
|
37
|
+
**Layout**: persists across navigations, state preserved.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// app/dashboard/layout.tsx
|
|
41
|
+
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
<nav>Sidebar — stays mounted</nav>
|
|
45
|
+
<main>{children}</main>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Template**: remounts on every navigation. Use when you need:
|
|
52
|
+
- Page transitions (AnimatePresence needs remount)
|
|
53
|
+
- `useEffect` that must re-run on navigation
|
|
54
|
+
- Resetting client state between pages
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// app/dashboard/template.tsx
|
|
58
|
+
'use client'
|
|
59
|
+
|
|
60
|
+
import { AnimatePresence, motion } from 'motion/react'
|
|
61
|
+
|
|
62
|
+
export default function Template({ children }: { children: React.ReactNode }) {
|
|
63
|
+
return (
|
|
64
|
+
<AnimatePresence mode="wait">
|
|
65
|
+
<motion.div
|
|
66
|
+
key={usePathname()}
|
|
67
|
+
initial={{ opacity: 0 }}
|
|
68
|
+
animate={{ opacity: 1 }}
|
|
69
|
+
exit={{ opacity: 0 }}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</motion.div>
|
|
73
|
+
</AnimatePresence>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Rule**: Layout wraps Template wraps Page: `layout.tsx > template.tsx > page.tsx`
|
|
79
|
+
|
|
80
|
+
## Route Groups — `(group)/`
|
|
81
|
+
|
|
82
|
+
Use parentheses to group routes without affecting the URL:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
app/
|
|
86
|
+
├── (marketing)/
|
|
87
|
+
│ ├── page.tsx → /
|
|
88
|
+
│ ├── about/page.tsx → /about
|
|
89
|
+
│ └── layout.tsx # Marketing layout
|
|
90
|
+
├── (dashboard)/
|
|
91
|
+
│ ├── dashboard/page.tsx → /dashboard
|
|
92
|
+
│ └── layout.tsx # Dashboard layout (different from marketing)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Parallel Routes — `@folder/`
|
|
96
|
+
|
|
97
|
+
Render multiple pages in the same layout simultaneously:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
app/
|
|
101
|
+
├── dashboard/
|
|
102
|
+
│ ├── layout.tsx # Accepts both props:
|
|
103
|
+
│ ├── @analytics/ # children, analytics, team
|
|
104
|
+
│ │ └── page.tsx
|
|
105
|
+
│ ├── @team/
|
|
106
|
+
│ │ └── page.tsx
|
|
107
|
+
│ └── page.tsx # Default children
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// app/dashboard/layout.tsx
|
|
112
|
+
export default function DashboardLayout(props: {
|
|
113
|
+
children: React.ReactNode
|
|
114
|
+
analytics: React.ReactNode
|
|
115
|
+
team: React.ReactNode
|
|
116
|
+
}) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="grid grid-cols-2">
|
|
119
|
+
<div>{props.children}</div>
|
|
120
|
+
<div>{props.analytics}</div>
|
|
121
|
+
<div>{props.team}</div>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Each slot needs a `default.tsx` for initial load and unmatched routes.
|
|
128
|
+
|
|
129
|
+
## Intercepting Routes — `(.)folder/`
|
|
130
|
+
|
|
131
|
+
Render a route in the context of another without full navigation:
|
|
132
|
+
|
|
133
|
+
| Convention | Meaning |
|
|
134
|
+
|-----------|---------|
|
|
135
|
+
| `(.)folder/` | Same level |
|
|
136
|
+
| `(..)folder/` | One level up |
|
|
137
|
+
| `(..)(..)folder/` | Two levels up |
|
|
138
|
+
| `(...)folder/` | From root |
|
|
139
|
+
|
|
140
|
+
Common pattern: Photo modal:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
app/
|
|
144
|
+
├── photos/
|
|
145
|
+
│ ├── page.tsx # Photos grid: /
|
|
146
|
+
│ └── [id]/
|
|
147
|
+
│ └── page.tsx # Photo detail: /photos/1
|
|
148
|
+
└── @modal/
|
|
149
|
+
├── default.tsx # null return
|
|
150
|
+
└── (.)photos/
|
|
151
|
+
└── [id]/
|
|
152
|
+
└── page.tsx # Modal overlay when navigating from photos grid
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// app/layout.tsx
|
|
157
|
+
export default function RootLayout({ children, modal }) {
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
{children}
|
|
161
|
+
{modal}
|
|
162
|
+
</>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Async Server Components
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// app/posts/[id]/page.tsx — no 'use client'
|
|
171
|
+
export default async function PostPage({
|
|
172
|
+
params,
|
|
173
|
+
}: {
|
|
174
|
+
params: Promise<{ id: string }> // params is a Promise in Next.js 15+
|
|
175
|
+
}) {
|
|
176
|
+
const { id } = await params
|
|
177
|
+
const post = await db.post.findUnique({ where: { id } })
|
|
178
|
+
|
|
179
|
+
return <article>{post.content}</article>
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Next.js 15+**: `params`, `searchParams` are Promises — must `await` them.
|
|
184
|
+
|
|
185
|
+
## Streaming with Suspense + loading.tsx
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// app/posts/page.tsx
|
|
189
|
+
import { Suspense } from 'react'
|
|
190
|
+
|
|
191
|
+
export default function PostsPage() {
|
|
192
|
+
return (
|
|
193
|
+
<div>
|
|
194
|
+
<h1>Posts</h1>
|
|
195
|
+
<Suspense fallback={<PostsSkeleton />}>
|
|
196
|
+
<PostsList /> {/* Streams in when ready */}
|
|
197
|
+
</Suspense>
|
|
198
|
+
<Suspense fallback={<StatsSkeleton />}>
|
|
199
|
+
<PostStats /> {/* Streams independently */}
|
|
200
|
+
</Suspense>
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `loading.tsx` wraps the entire page in Suspense automatically
|
|
207
|
+
- Manual `<Suspense>` gives finer control — stream sections independently
|
|
208
|
+
- Wrap data-fetching Server Components in Suspense
|
|
209
|
+
|
|
210
|
+
## RSC Boundary Rules
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
Server Component (default)
|
|
214
|
+
└─ can render → Client Components
|
|
215
|
+
└─ can pass → serializable props only (no functions, no JSX as props)
|
|
216
|
+
└─ can use → async/await, direct DB access, filesystem, secrets
|
|
217
|
+
|
|
218
|
+
Client Component ('use client')
|
|
219
|
+
└─ can render → Server Components (passed as children)
|
|
220
|
+
└─ can use → hooks, event handlers, browser APIs, state
|
|
221
|
+
└─ cannot → async/await directly, access DB
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Boundary pattern** — push 'use client' as deep as possible:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
// ✅ Server Component (default) — data fetching here
|
|
228
|
+
export default async function UserProfile({ userId }) {
|
|
229
|
+
const user = await db.user.findUnique({ where: { id: userId } })
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div>
|
|
233
|
+
<h1>{user.name}</h1>
|
|
234
|
+
<EditButton /> {/* Only the interactive leaf is client */}
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ✅ Client leaf — only what needs interactivity
|
|
240
|
+
'use client'
|
|
241
|
+
function EditButton() {
|
|
242
|
+
const [open, setOpen] = useState(false)
|
|
243
|
+
return <button onClick={() => setOpen(true)}>Edit</button>
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Error Handling
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
// app/dashboard/error.tsx
|
|
251
|
+
'use client'
|
|
252
|
+
|
|
253
|
+
export default function DashboardError({
|
|
254
|
+
error,
|
|
255
|
+
reset,
|
|
256
|
+
}: {
|
|
257
|
+
error: Error & { digest?: string }
|
|
258
|
+
reset: () => void
|
|
259
|
+
}) {
|
|
260
|
+
return (
|
|
261
|
+
<div>
|
|
262
|
+
<h2>Something went wrong!</h2>
|
|
263
|
+
<button onClick={() => reset()}>Try again</button>
|
|
264
|
+
</div>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- `error.tsx` must be a Client Component
|
|
270
|
+
- Errors bubble up to the nearest `error.tsx`
|
|
271
|
+
- `reset()` re-renders the error boundary's children
|
|
272
|
+
- `error.tsx` in a nested segment only catches errors in that segment and below
|
|
273
|
+
|
|
274
|
+
## Middleware
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
// middleware.ts (root level)
|
|
278
|
+
import { NextResponse } from 'next/server'
|
|
279
|
+
import type { NextRequest } from 'next/server'
|
|
280
|
+
|
|
281
|
+
export function middleware(request: NextRequest) {
|
|
282
|
+
const token = request.cookies.get('token')
|
|
283
|
+
|
|
284
|
+
// Protect routes
|
|
285
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
286
|
+
return NextResponse.redirect(new URL('/login', request.url))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return NextResponse.next()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const config = {
|
|
293
|
+
matcher: ['/dashboard/:path*']
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Metadata API
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
// Static metadata
|
|
301
|
+
export const metadata: Metadata = {
|
|
302
|
+
title: 'Dashboard',
|
|
303
|
+
description: 'Manage your account',
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Dynamic metadata (Server Components)
|
|
307
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
308
|
+
const post = await getPost(params.id)
|
|
309
|
+
return { title: post.title, description: post.excerpt }
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Common Pitfalls
|
|
314
|
+
|
|
315
|
+
| Pitfall | Fix |
|
|
316
|
+
|---------|-----|
|
|
317
|
+
| Adding `'use client'` to a layout | Layouts are Server Components by default; only add `'use client'` when you need hooks |
|
|
318
|
+
| Forgetting `default.tsx` for parallel routes | Every slot needs a `default.tsx` for initial load |
|
|
319
|
+
| Passing functions as props from Server to Client | Functions are not serializable — define them in the client |
|
|
320
|
+
| Using `useSearchParams()` in Server Component | Use `searchParams` prop (Promise in v15+) |
|
|
321
|
+
| `params` treated as plain object (v15+) | `params` is now a Promise — must `await params` |
|
|
322
|
+
| No Suspense boundary for async component | Wrap in `<Suspense>` or ensure `loading.tsx` exists |
|
|
323
|
+
| `layout.tsx` doesn't receive `searchParams` | Only `page.tsx` gets `searchParams` |
|
|
324
|
+
|
|
325
|
+
## Verification
|
|
326
|
+
|
|
327
|
+
- [ ] Server Components are the default — `'use client'` only where interactive
|
|
328
|
+
- [ ] `params` and `searchParams` are awaited (Next.js 15+)
|
|
329
|
+
- [ ] Error boundaries (`error.tsx`) exist at appropriate levels
|
|
330
|
+
- [ ] Loading states (`loading.tsx` or `<Suspense>`) for async pages
|
|
331
|
+
- [ ] Parallel route slots each have `default.tsx`
|
|
332
|
+
- [ ] Layout and template used correctly (persist vs remount)
|
|
333
|
+
- [ ] Route groups used to share layouts without affecting URL
|
|
334
|
+
- [ ] Functions and non-serializable data stay in Client Components
|