@thorprovider/create-storefront 0.1.1
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/README.md +119 -0
- package/bin/install.js +116 -0
- package/commands/sf-add-view.md +21 -0
- package/commands/sf-init.md +16 -0
- package/commands/sf-theme.md +15 -0
- package/commands/sf-view.md +20 -0
- package/package.json +40 -0
- package/recipes/archetype.schema.json +39 -0
- package/recipes/archetypes.json +148 -0
- package/recipes/recipe.schema.json +59 -0
- package/recipes/recipes.json +90 -0
- package/recipes/sections.json +46 -0
- package/recipes/validate.mjs +190 -0
- package/skills/building-storefronts/SKILL.md +178 -0
- package/skills/building-storefronts/references/frontend-integration.md +229 -0
- package/skills/json-render-core/SKILL.md +291 -0
- package/skills/json-render-next/SKILL.md +194 -0
- package/skills/json-render-react/SKILL.md +298 -0
- package/skills/json-render-remotion/SKILL.md +111 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/json-render-solid/SKILL.md +204 -0
- package/skills/nextjs-shadcn/SKILL.md +303 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +127 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +258 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-scaffold/SKILL.md +118 -0
- package/skills/sf-theme-gen/SKILL.md +44 -0
- package/skills/sf-view-gen/SKILL.md +94 -0
- package/skills/shadcn-component-discovery/SKILL.md +273 -0
- package/skills/shadcn-component-discovery/references/registries.md +226 -0
- package/skills/shadcn-theming/SKILL.md +104 -0
- package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
- package/skills/shadcn-theming/references/theming-guide.md +90 -0
- package/skills/storefront-best-practices/SKILL.md +421 -0
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
- package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
- package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
- package/skills/storefront-best-practices/reference/components/footer.md +112 -0
- package/skills/storefront-best-practices/reference/components/hero.md +241 -0
- package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
- package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
- package/skills/storefront-best-practices/reference/components/popups.md +221 -0
- package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
- package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
- package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
- package/skills/storefront-best-practices/reference/components/search.md +101 -0
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
- package/skills/storefront-best-practices/reference/design.md +388 -0
- package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
- package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
- package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
- package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
- package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
- package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
- package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
- package/skills/storefront-best-practices/reference/medusa.md +307 -0
- package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
- package/skills/storefront-best-practices/reference/seo.md +195 -0
- package/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/templates/app/app/globals.css +101 -0
- package/templates/app/app/layout.tsx +35 -0
- package/templates/app/lib/__STOREFRONT__/catalog.ts +132 -0
- package/templates/app/lib/__STOREFRONT__/handlers.ts +33 -0
- package/templates/app/lib/__STOREFRONT__/registry.tsx +134 -0
- package/templates/app/lib/__STOREFRONT__/runtime.ts +25 -0
- package/templates/app/lib/__STOREFRONT__/spec/home.ts +62 -0
- package/templates/app/lib/__STOREFRONT__/spec/index.ts +59 -0
- package/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/templates/app/lib/__STOREFRONT__/state.ts +35 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Best Practices
|
|
4
|
+
|
|
5
|
+
- Avoid `useEffect` - prefer Server Components, Server Actions, or event handlers
|
|
6
|
+
- `"use client"` only at leaf components (smallest boundary)
|
|
7
|
+
- Props must be serializable (data or Server Actions, no functions/classes)
|
|
8
|
+
- Prefer Tailwind v4 `globals.css` theme variables over hardcoded values
|
|
9
|
+
|
|
10
|
+
## Component Patterns
|
|
11
|
+
|
|
12
|
+
### Server vs Client Decision Tree
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Need state/effects/browser APIs?
|
|
16
|
+
├── Yes → "use client" at smallest boundary
|
|
17
|
+
└── No → Server Component (default)
|
|
18
|
+
|
|
19
|
+
Passing data to client?
|
|
20
|
+
├── Functions/classes → ❌ Not serializable
|
|
21
|
+
├── Plain objects/arrays → ✅ Props
|
|
22
|
+
└── Server logic → ✅ Server Actions
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Component Placement
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
app/
|
|
29
|
+
├── (protected)/ # Auth required routes
|
|
30
|
+
│ ├── dashboard/
|
|
31
|
+
│ ├── settings/
|
|
32
|
+
│ ├── components/ # Route-specific components
|
|
33
|
+
│ └── lib/ # Route-specific types/utils
|
|
34
|
+
├── (public)/ # Public routes
|
|
35
|
+
│ ├── login/
|
|
36
|
+
│ └── register/
|
|
37
|
+
├── actions/ # Server Actions (global)
|
|
38
|
+
├── api/ # API routes
|
|
39
|
+
components/ # Shared across routes
|
|
40
|
+
├── ui/ # shadcn primitives
|
|
41
|
+
└── shared/ # Business components
|
|
42
|
+
hooks/ # Custom React hooks
|
|
43
|
+
lib/ # Shared utilities
|
|
44
|
+
data/ # Database queries
|
|
45
|
+
ai/ # AI logic (tools, agents, prompts)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### AI Directory Structure
|
|
49
|
+
|
|
50
|
+
When building AI applications, organize the `ai/` directory:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
ai/
|
|
54
|
+
├── model-names.ts # Model definitions & DEFAULT_MODEL_NAME
|
|
55
|
+
├── actions/ # AI-related server actions
|
|
56
|
+
│ ├── model.ts # saveModelId, getModelId (cookie-based)
|
|
57
|
+
│ └── chat.ts # Chat-related actions
|
|
58
|
+
├── utils.ts # findSources, getLastUserMessageText, etc.
|
|
59
|
+
├── agents/ # Agent definitions (if using agents)
|
|
60
|
+
│ └── assistant.ts
|
|
61
|
+
└── tools/ # Tool definitions (if using tools)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**model-names.ts example:**
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
export interface Model {
|
|
68
|
+
id: string
|
|
69
|
+
label: string
|
|
70
|
+
description: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const models: Model[] = [
|
|
74
|
+
{ id: "gpt-5.6-luna", label: "GPT 5.6 Luna", description: "Fast, lightweight tasks" },
|
|
75
|
+
{ id: "gpt-5.6", label: "GPT 5.6", description: "Complex, multi-step tasks" },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
export const DEFAULT_MODEL_NAME = "gpt-5.6-luna"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Cookie-based model storage:**
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// ai/actions/model.ts - Server Action for MUTATION only
|
|
85
|
+
"use server"
|
|
86
|
+
import { cookies } from "next/headers"
|
|
87
|
+
|
|
88
|
+
export async function saveModelId(model: string) {
|
|
89
|
+
const cookieStore = await cookies()
|
|
90
|
+
cookieStore.set("model-id", model)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ❌ WRONG: Don't use Server Action for reading data
|
|
94
|
+
// export async function getModelId() { ... }
|
|
95
|
+
|
|
96
|
+
// ✅ CORRECT: Read cookies directly in Server Component
|
|
97
|
+
// page.tsx
|
|
98
|
+
import { cookies } from "next/headers"
|
|
99
|
+
|
|
100
|
+
export default async function Page() {
|
|
101
|
+
const cookieStore = await cookies()
|
|
102
|
+
const modelId = cookieStore.get("model-id")?.value ?? DEFAULT_MODEL_NAME
|
|
103
|
+
return <Chat modelId={modelId} />
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### className Pattern
|
|
108
|
+
|
|
109
|
+
Always accept and merge `className`:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { cn } from "@/lib/utils"
|
|
113
|
+
|
|
114
|
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
115
|
+
variant?: "default" | "outline"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function Card({ className, variant = "default", ...props }: CardProps) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
className={cn(
|
|
122
|
+
"rounded-lg p-4",
|
|
123
|
+
variant === "outline" && "border",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Data Fetching Patterns
|
|
133
|
+
|
|
134
|
+
### Server Component (default)
|
|
135
|
+
|
|
136
|
+
Fetch data directly in Server Components:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
export default async function Page() {
|
|
140
|
+
const data = await fetchData()
|
|
141
|
+
return <Component data={data} />
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Cached Data Function
|
|
146
|
+
|
|
147
|
+
Use `'use cache'` for reusable cached queries. `"use cache"` (and `cacheTag`/`cacheLife`/`updateTag`) requires the Cache Components opt-in flag — Next.js 16 does not enable it by default:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// next.config.ts
|
|
151
|
+
const nextConfig = { cacheComponents: true }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
// data/products.ts
|
|
156
|
+
export async function getProducts() {
|
|
157
|
+
"use cache"
|
|
158
|
+
cacheTag("products")
|
|
159
|
+
cacheLife("hours")
|
|
160
|
+
return await db.products.findMany()
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Streaming to Client (React `use` hook)
|
|
165
|
+
|
|
166
|
+
Pass promises to Client Components for streaming:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// Server Component
|
|
170
|
+
export default function Page() {
|
|
171
|
+
const dataPromise = fetchData() // Don't await
|
|
172
|
+
return (
|
|
173
|
+
<Suspense fallback={<Loading />}>
|
|
174
|
+
<ClientDisplay dataPromise={dataPromise} />
|
|
175
|
+
</Suspense>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Client Component
|
|
180
|
+
"use client"
|
|
181
|
+
import { use } from "react"
|
|
182
|
+
|
|
183
|
+
export function ClientDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
|
|
184
|
+
const data = use(dataPromise) // Suspends until resolved
|
|
185
|
+
return <Chart data={data} />
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Explicit Request-time with connection()
|
|
190
|
+
|
|
191
|
+
Use `connection()` to explicitly defer to request time without accessing runtime APIs:
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { connection } from "next/server"
|
|
195
|
+
import { Suspense } from "react"
|
|
196
|
+
|
|
197
|
+
async function UniqueContent() {
|
|
198
|
+
await connection() // Defer to request time
|
|
199
|
+
const uuid = crypto.randomUUID()
|
|
200
|
+
const timestamp = Date.now()
|
|
201
|
+
return <div>{uuid} - {timestamp}</div>
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default function Page() {
|
|
205
|
+
return (
|
|
206
|
+
<Suspense fallback={<Loading />}>
|
|
207
|
+
<UniqueContent />
|
|
208
|
+
</Suspense>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**When to use `connection()`:**
|
|
214
|
+
|
|
215
|
+
| Scenario | Use connection()? |
|
|
216
|
+
|----------|-------------------|
|
|
217
|
+
| Need unique values per request | ✅ Yes |
|
|
218
|
+
| Using `Math.random()`, `Date.now()`, `crypto.randomUUID()` | ✅ Yes |
|
|
219
|
+
| Already using `cookies()` or `headers()` | ❌ No (not needed) |
|
|
220
|
+
| Data is cacheable | ❌ No (use `'use cache'`) |
|
|
221
|
+
|
|
222
|
+
## Routing
|
|
223
|
+
|
|
224
|
+
### Route Groups
|
|
225
|
+
|
|
226
|
+
Group routes without affecting URL:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
app/
|
|
230
|
+
├── (protected)/ # Auth required - /dashboard, /settings
|
|
231
|
+
│ ├── dashboard/
|
|
232
|
+
│ ├── settings/
|
|
233
|
+
│ └── layout.tsx # Shared chrome (sidebar, auth check)
|
|
234
|
+
├── (public)/ # Public - /login, /register, /about
|
|
235
|
+
│ ├── login/
|
|
236
|
+
│ ├── register/
|
|
237
|
+
│ └── about/
|
|
238
|
+
└── (marketing)/ # Marketing - /pricing, /features
|
|
239
|
+
├── pricing/
|
|
240
|
+
└── features/
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Layout vs Template
|
|
244
|
+
|
|
245
|
+
| Aspect | layout.tsx | template.tsx |
|
|
246
|
+
|--------|------------|--------------|
|
|
247
|
+
| State | Persists across navigation | Resets on navigation |
|
|
248
|
+
| Effects | Run once | Run on every navigation |
|
|
249
|
+
| Use when | Shared chrome (nav, footer) | Analytics, animations that reset |
|
|
250
|
+
|
|
251
|
+
**Decision tree:**
|
|
252
|
+
```
|
|
253
|
+
State/effects should reset on navigation?
|
|
254
|
+
├── Yes → template.tsx
|
|
255
|
+
└── No → layout.tsx (default)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Async Params (Next.js 16)
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
// Always await params and searchParams
|
|
262
|
+
export default async function Page({
|
|
263
|
+
params,
|
|
264
|
+
searchParams,
|
|
265
|
+
}: {
|
|
266
|
+
params: Promise<{ slug: string }>
|
|
267
|
+
searchParams: Promise<{ page?: string }>
|
|
268
|
+
}) {
|
|
269
|
+
const { slug } = await params
|
|
270
|
+
const { page = "1" } = await searchParams
|
|
271
|
+
|
|
272
|
+
const data = await fetchData(slug, parseInt(page))
|
|
273
|
+
return <Content data={data} />
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Suspense Strategy
|
|
278
|
+
|
|
279
|
+
### When to Use Suspense
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
Slow data fetch in Server Component?
|
|
283
|
+
├── Yes → Wrap in <Suspense>
|
|
284
|
+
└── No → Direct render
|
|
285
|
+
|
|
286
|
+
Multiple independent slow sections?
|
|
287
|
+
├── Yes → Separate <Suspense> boundaries
|
|
288
|
+
└── No → Single boundary or loading.tsx
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Patterns
|
|
292
|
+
|
|
293
|
+
**loading.tsx** - Entire route fallback:
|
|
294
|
+
```tsx
|
|
295
|
+
// app/dashboard/loading.tsx
|
|
296
|
+
export default function Loading() {
|
|
297
|
+
return <DashboardSkeleton />
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Suspense** - Granular streaming:
|
|
302
|
+
```tsx
|
|
303
|
+
export default function Page() {
|
|
304
|
+
return (
|
|
305
|
+
<>
|
|
306
|
+
<Header /> {/* Renders immediately */}
|
|
307
|
+
<Suspense fallback={<StatsSkeleton />}>
|
|
308
|
+
<SlowStats /> {/* Streams when ready */}
|
|
309
|
+
</Suspense>
|
|
310
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
311
|
+
<SlowChart /> {/* Streams independently */}
|
|
312
|
+
</Suspense>
|
|
313
|
+
</>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Skeleton pattern** - Create a skeleton component for each loadable content:
|
|
319
|
+
```tsx
|
|
320
|
+
// components/skeletons.tsx
|
|
321
|
+
export function CardSkeleton() {
|
|
322
|
+
return (
|
|
323
|
+
<div className="animate-pulse">
|
|
324
|
+
<div className="h-4 bg-muted rounded w-3/4 mb-2" />
|
|
325
|
+
<div className="h-4 bg-muted rounded w-1/2" />
|
|
326
|
+
</div>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
|
331
|
+
return (
|
|
332
|
+
<div className="space-y-2">
|
|
333
|
+
{Array.from({ length: rows }).map((_, i) => (
|
|
334
|
+
<div key={i} className="h-10 bg-muted rounded animate-pulse" />
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Passing promises to client**:
|
|
342
|
+
```tsx
|
|
343
|
+
// Server Component
|
|
344
|
+
export default function Page() {
|
|
345
|
+
const dataPromise = fetchData() // Start fetch, don't await
|
|
346
|
+
return <ClientChart dataPromise={dataPromise} />
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Client Component
|
|
350
|
+
"use client"
|
|
351
|
+
import { use } from "react"
|
|
352
|
+
|
|
353
|
+
export function ClientChart({ dataPromise }) {
|
|
354
|
+
const data = use(dataPromise) // Suspends until resolved
|
|
355
|
+
return <Chart data={data} />
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## State Management
|
|
360
|
+
|
|
361
|
+
### useTransition Pattern
|
|
362
|
+
|
|
363
|
+
Wrap non-urgent UI updates to keep interactions smooth:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
"use client"
|
|
367
|
+
import { useTransition } from "react"
|
|
368
|
+
|
|
369
|
+
function SubmitButton({ action }: { action: () => Promise<void> }) {
|
|
370
|
+
const [isPending, startTransition] = useTransition()
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<button
|
|
374
|
+
onClick={() => startTransition(() => action())}
|
|
375
|
+
disabled={isPending}
|
|
376
|
+
>
|
|
377
|
+
{isPending ? "Saving..." : "Save"}
|
|
378
|
+
</button>
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Guidelines:**
|
|
384
|
+
- Use `isPending` for feedback (disable buttons, show spinners)
|
|
385
|
+
- Don't wrap controlled input state in transitions
|
|
386
|
+
- After `await` inside transition, wrap subsequent `setState` in another `startTransition`
|
|
387
|
+
|
|
388
|
+
## Data Patterns
|
|
389
|
+
|
|
390
|
+
### "use cache" (Next.js 16)
|
|
391
|
+
|
|
392
|
+
Function-level caching:
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
"use cache"
|
|
396
|
+
|
|
397
|
+
export async function getProducts() {
|
|
398
|
+
const products = await db.query.products.findMany()
|
|
399
|
+
return products
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// With cache tags
|
|
403
|
+
import { cacheTag } from "next/cache"
|
|
404
|
+
|
|
405
|
+
export async function getProduct(id: string) {
|
|
406
|
+
"use cache"
|
|
407
|
+
cacheTag(`product-${id}`)
|
|
408
|
+
return db.query.products.findFirst({ where: eq(products.id, id) })
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Server Actions
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
"use server"
|
|
416
|
+
|
|
417
|
+
import { updateTag, revalidateTag } from "next/cache"
|
|
418
|
+
import { z } from "zod"
|
|
419
|
+
|
|
420
|
+
const schema = z.object({
|
|
421
|
+
title: z.string().min(1),
|
|
422
|
+
content: z.string(),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
export async function createPost(formData: FormData) {
|
|
426
|
+
const parsed = schema.parse({
|
|
427
|
+
title: formData.get("title"),
|
|
428
|
+
content: formData.get("content"),
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
await db.insert(posts).values(parsed)
|
|
432
|
+
|
|
433
|
+
// Read-your-writes (immediate)
|
|
434
|
+
updateTag("posts")
|
|
435
|
+
|
|
436
|
+
// Or SWR-style revalidation
|
|
437
|
+
// revalidateTag("posts", "max")
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Refresh uncached data
|
|
441
|
+
import { refresh } from "next/cache"
|
|
442
|
+
|
|
443
|
+
export async function updateProfile(data: FormData) {
|
|
444
|
+
await db.update(...)
|
|
445
|
+
refresh() // Triggers client router refresh
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Proxy API (Next.js 16)
|
|
450
|
+
|
|
451
|
+
Replaces middleware for request interception. Place at project root (same level as `app/`):
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
// proxy.ts (project root)
|
|
455
|
+
import { NextResponse } from "next/server"
|
|
456
|
+
import type { NextRequest } from "next/server"
|
|
457
|
+
|
|
458
|
+
export async function proxy(request: NextRequest) {
|
|
459
|
+
const session = request.cookies.get("session")
|
|
460
|
+
|
|
461
|
+
if (!session && request.nextUrl.pathname.startsWith("/dashboard")) {
|
|
462
|
+
return NextResponse.redirect(new URL("/login", request.url))
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return NextResponse.next()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export const config = {
|
|
469
|
+
matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Request APIs
|
|
474
|
+
|
|
475
|
+
All request APIs are async in Next.js 16:
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
import { cookies, headers, draftMode } from "next/headers"
|
|
479
|
+
|
|
480
|
+
export default async function Page() {
|
|
481
|
+
const cookieStore = await cookies()
|
|
482
|
+
const headersList = await headers()
|
|
483
|
+
const { isEnabled } = await draftMode()
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Error Handling
|
|
488
|
+
|
|
489
|
+
Define only when custom UX needed:
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
app/
|
|
493
|
+
├── error.tsx # Route-level errors
|
|
494
|
+
├── global-error.tsx # Root layout errors
|
|
495
|
+
├── not-found.tsx # 404 pages
|
|
496
|
+
└── loading.tsx # Loading states
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Otherwise inherit from parent segment.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Project Setup
|
|
2
|
+
|
|
3
|
+
## Create New Project
|
|
4
|
+
|
|
5
|
+
### Minimal Setup
|
|
6
|
+
|
|
7
|
+
Use the CLI to scaffold a Next.js project directly:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx --bun shadcn@latest init --template next --base base
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`--base` picks the primitive library: `base` (Base UI, default), `radix`, or
|
|
14
|
+
`aria` (React Aria). Component APIs differ per base — see
|
|
15
|
+
[shadcn-platform.md](shadcn-platform.md). Add `--rtl` if the app needs
|
|
16
|
+
right-to-left support.
|
|
17
|
+
|
|
18
|
+
### With Preset Code
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bunx --bun shadcn@latest init --preset <CODE> --template next
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Preset codes are short opaque strings from [ui.shadcn.com/create](https://ui.shadcn.com/create) — pass them through verbatim rather than expanding them into individual flags. Prefer this when you want a specific visual system without hardcoding individual style, font, or icon choices into the skill:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bunx --bun shadcn@latest init --preset b4h07r5A1 --template next
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`shadcn preset decode <code>` prints what a code contains (style, base color,
|
|
31
|
+
theme, font, icon library, radius) if you need to read it before applying.
|
|
32
|
+
|
|
33
|
+
For a project that already exists, apply a preset instead of re-running `init`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bunx --bun shadcn@latest apply <CODE> # theme, fonts, components
|
|
37
|
+
bunx --bun shadcn@latest apply <CODE> --only theme # just the theme
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Monorepo
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bunx --bun shadcn@latest init \
|
|
44
|
+
--template next \
|
|
45
|
+
--monorepo
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Existing Project
|
|
49
|
+
|
|
50
|
+
If the Next.js app already exists, run the initializer from the project root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bunx --bun shadcn@latest init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Inspect Before Changing
|
|
57
|
+
|
|
58
|
+
Use the CLI to inspect project state or preview registry changes before writing files:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bunx --bun shadcn@latest info
|
|
62
|
+
bunx --bun shadcn@latest add button --dry-run
|
|
63
|
+
bunx --bun shadcn@latest docs button
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Add Components
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Single component
|
|
70
|
+
bunx --bun shadcn@latest add button
|
|
71
|
+
|
|
72
|
+
# Multiple components
|
|
73
|
+
bunx --bun shadcn@latest add button card input
|
|
74
|
+
|
|
75
|
+
# All components
|
|
76
|
+
bunx --bun shadcn@latest add --all
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Common Dependencies
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Forms
|
|
83
|
+
bun add react-hook-form @hookform/resolvers zod
|
|
84
|
+
|
|
85
|
+
# AI
|
|
86
|
+
bun add ai @ai-sdk/anthropic
|
|
87
|
+
|
|
88
|
+
# Animation
|
|
89
|
+
bun add motion # For Motion
|
|
90
|
+
bun add gsap @gsap/react # For GSAP
|
|
91
|
+
|
|
92
|
+
# Icons (pick one)
|
|
93
|
+
bun add lucide-react # Default
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Project Structure After Setup
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
project/
|
|
100
|
+
├── app/
|
|
101
|
+
│ ├── globals.css # Theme tokens
|
|
102
|
+
│ ├── layout.tsx # Root layout
|
|
103
|
+
│ └── page.tsx # Home page
|
|
104
|
+
├── components/
|
|
105
|
+
│ └── ui/ # shadcn components
|
|
106
|
+
├── lib/
|
|
107
|
+
│ └── utils.ts # cn() helper
|
|
108
|
+
├── public/
|
|
109
|
+
├── components.json # shadcn config
|
|
110
|
+
├── tsconfig.json
|
|
111
|
+
└── package.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Bun Commands Reference
|
|
115
|
+
|
|
116
|
+
| Task | Command |
|
|
117
|
+
|------|---------|
|
|
118
|
+
| Install deps | `bun install` |
|
|
119
|
+
| Add package | `bun add package` |
|
|
120
|
+
| Dev server | `bun --bun next dev` |
|
|
121
|
+
| Build | `bun --bun next build` |
|
|
122
|
+
| Start prod | `bun --bun next start` |
|
|
123
|
+
| Add shadcn component | `bunx --bun shadcn@latest add component` |
|
|
124
|
+
| Create project | `bunx --bun shadcn@latest init --template next --base base` |
|
|
125
|
+
| Read project config | `bunx --bun shadcn@latest info --json` |
|
|
126
|
+
| Component API docs | `bunx --bun shadcn@latest docs component` |
|
|
127
|
+
|