@thorprovider/storefront 0.3.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/CHANGELOG.md +61 -0
- package/README.md +333 -0
- package/assets/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/assets/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/assets/templates/app/app/globals.css +101 -0
- package/assets/templates/app/app/layout.tsx +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +88 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +42 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +47 -0
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +98 -0
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +17 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +48 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/index.ts +7 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +33 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +28 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +46 -0
- package/assets/templates/views/home/index.tsx +142 -0
- package/assets/templates/views/home/registry.tsx +65 -0
- package/bin/storefront.js +531 -0
- package/commands/sf-add-view.md +26 -0
- package/commands/sf-theme.md +20 -0
- package/commands/sf-view.md +25 -0
- package/package.json +48 -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/scripts/render-templates.mjs +255 -0
- package/scripts/sync-assets.mjs +87 -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 +367 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +162 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +261 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-theme-gen/SKILL.md +61 -0
- package/skills/sf-view-gen/SKILL.md +116 -0
- package/skills/sf-view-gen/assets/recipes/archetypes.json +148 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +90 -0
- package/skills/sf-view-gen/assets/recipes/sections.json +46 -0
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +86 -0
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +48 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +192 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +45 -0
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +122 -0
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +49 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +168 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +49 -0
- package/skills/sf-view-gen/references/views.md +69 -0
- package/skills/sf-view-gen/scripts/validate.mjs +190 -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
|
@@ -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,162 @@
|
|
|
1
|
+
# Project Setup
|
|
2
|
+
|
|
3
|
+
## Create New Project
|
|
4
|
+
|
|
5
|
+
### Minimal Setup
|
|
6
|
+
|
|
7
|
+
Use the CLI to scaffold a Next.js project directly. Supply every prompt value as
|
|
8
|
+
a flag so the command never reads stdin:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
yarn dlx shadcn@latest init \
|
|
12
|
+
--template next --base base --preset <preset> \
|
|
13
|
+
--no-monorepo --no-rtl --yes --name <project-name>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`--base` picks the primitive library: `base` (Base UI, default), `radix`, or
|
|
17
|
+
`aria` (React Aria). Component APIs differ per base — see
|
|
18
|
+
[shadcn-platform.md](shadcn-platform.md). Add `--rtl` if the app needs
|
|
19
|
+
right-to-left support.
|
|
20
|
+
|
|
21
|
+
`--name <project-name>` makes the CLI create a **new nested project directory**
|
|
22
|
+
(`<cwd>/<project-name>/`). Omit it to initialize **in place** in the current
|
|
23
|
+
directory (or in `--cwd`). In-place init requires an existing `package.json` —
|
|
24
|
+
with an empty directory the wizard always creates the nested project, and the
|
|
25
|
+
escape `--name .` fails with `dest already exists`. For an empty directory, run
|
|
26
|
+
`yarn create next-app . --ts --tailwind --app --import-alias "@/*"` first
|
|
27
|
+
(moving any `.opencode/` aside and restoring it
|
|
28
|
+
after — `create-next-app` refuses non-empty dirs), then `shadcn init` in place.
|
|
29
|
+
The Thor scaffold flow (`npx @thorprovider/storefront create`) follows exactly that
|
|
30
|
+
two-step flow and never uses shadcn's `--name`, so the project has a single root. Any
|
|
31
|
+
interactive prompt means a flag is missing — resolve it from `--help`, never by guessing.
|
|
32
|
+
|
|
33
|
+
### Yarn linker (required)
|
|
34
|
+
|
|
35
|
+
Write `.yarnrc.yml` at the project root **before installing dependencies**:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
nodeLinker: node-modules
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Yarn 4 defaults to the Plug'n'Play (PnP) linker, which Next.js 16 Turbopack cannot
|
|
42
|
+
resolve from (`Could not find the Next.js package`). The `node-modules` linker makes
|
|
43
|
+
Yarn create a real `node_modules/`, which Turbopack reads. Vercel does not plan PnP
|
|
44
|
+
support for Turbopack.
|
|
45
|
+
|
|
46
|
+
### With Preset Code
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
yarn dlx shadcn@latest init \
|
|
50
|
+
--template next --base base --preset <CODE> \
|
|
51
|
+
--no-monorepo --no-rtl --yes --name <project-name>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
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:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
yarn dlx shadcn@latest init \
|
|
58
|
+
--template next --base base --preset b4h07r5A1 \
|
|
59
|
+
--no-monorepo --no-rtl --yes --name my-app
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`shadcn preset decode <code>` prints what a code contains (style, base color,
|
|
63
|
+
theme, font, icon library, radius) if you need to read it before applying.
|
|
64
|
+
|
|
65
|
+
For a project that already exists, apply a preset instead of re-running `init`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
yarn dlx shadcn@latest apply <CODE> # theme, fonts, components
|
|
69
|
+
yarn dlx shadcn@latest apply <CODE> --only theme # just the theme
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Monorepo
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
yarn dlx shadcn@latest init \
|
|
76
|
+
--template next \
|
|
77
|
+
--monorepo
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Existing Project
|
|
81
|
+
|
|
82
|
+
If the Next.js app already exists, run the initializer from the project root:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
yarn dlx shadcn@latest init \
|
|
86
|
+
--template next --base base --preset <preset> \
|
|
87
|
+
--no-monorepo --no-rtl --yes
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Inspect Before Changing
|
|
91
|
+
|
|
92
|
+
Use the CLI to inspect project state or preview registry changes before writing files:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
yarn dlx shadcn@latest info
|
|
96
|
+
yarn dlx shadcn@latest add button --dry-run
|
|
97
|
+
yarn dlx shadcn@latest docs button
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Add Components
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Single component
|
|
104
|
+
yarn dlx shadcn@latest add button
|
|
105
|
+
|
|
106
|
+
# Multiple components
|
|
107
|
+
yarn dlx shadcn@latest add button card input
|
|
108
|
+
|
|
109
|
+
# All components
|
|
110
|
+
yarn dlx shadcn@latest add --all
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Common Dependencies
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Forms
|
|
117
|
+
yarn add react-hook-form @hookform/resolvers zod
|
|
118
|
+
|
|
119
|
+
# AI
|
|
120
|
+
yarn add ai @ai-sdk/anthropic
|
|
121
|
+
|
|
122
|
+
# Animation
|
|
123
|
+
yarn add motion # For Motion
|
|
124
|
+
yarn add gsap @gsap/react # For GSAP
|
|
125
|
+
|
|
126
|
+
# Icons (pick one)
|
|
127
|
+
yarn add lucide-react # Default
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Project Structure After Setup
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
project/
|
|
134
|
+
├── app/
|
|
135
|
+
│ ├── globals.css # Theme tokens
|
|
136
|
+
│ ├── layout.tsx # Root layout
|
|
137
|
+
│ └── page.tsx # Home page
|
|
138
|
+
├── components/
|
|
139
|
+
│ └── ui/ # shadcn components
|
|
140
|
+
├── lib/
|
|
141
|
+
│ └── utils.ts # cn() helper
|
|
142
|
+
├── public/
|
|
143
|
+
├── components.json # shadcn config
|
|
144
|
+
├── tsconfig.json
|
|
145
|
+
└── package.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Yarn Commands Reference
|
|
149
|
+
|
|
150
|
+
| Task | Command |
|
|
151
|
+
|------|---------|
|
|
152
|
+
| Install deps | `yarn install` |
|
|
153
|
+
| Add package | `yarn add package` |
|
|
154
|
+
| Dev server | `yarn dev` |
|
|
155
|
+
| Build | `yarn build` |
|
|
156
|
+
| Start prod | `yarn start` |
|
|
157
|
+
| Add shadcn component | `yarn dlx shadcn@latest add component` |
|
|
158
|
+
| Create project | `yarn dlx shadcn@latest init --template next --base base --preset <preset> --no-monorepo --no-rtl --yes --name <project-name>` |
|
|
159
|
+
| Read project config | `yarn dlx shadcn@latest info --json` |
|
|
160
|
+
| Component API docs | `yarn dlx shadcn@latest docs component` |
|
|
161
|
+
|
|
162
|
+
|