@plazmodium/odin 0.3.2-beta → 0.3.4-beta
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 +82 -11
- package/builtin/ODIN.md +1045 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +716 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +363 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +430 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +2 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-dev
|
|
3
|
+
description: Next.js development — App Router, Server Components, Server Actions, caching, data fetching patterns, and deployment
|
|
4
|
+
category: frontend
|
|
5
|
+
version: "14.x - 16.x"
|
|
6
|
+
depends_on:
|
|
7
|
+
- react-patterns
|
|
8
|
+
compatible_with:
|
|
9
|
+
- tailwindcss
|
|
10
|
+
- supabase
|
|
11
|
+
- prisma-orm
|
|
12
|
+
- typescript
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Next.js Development
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
Next.js 14+ with App Router provides Server Components by default, Server Actions for mutations, and streaming for progressive rendering. Next.js 16 introduces Cache Components (`use cache` directive) and makes request-bound APIs async. Prefer the App Router for all new projects.
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
app/
|
|
25
|
+
├── layout.tsx # Root layout (wraps all pages)
|
|
26
|
+
├── page.tsx # Home page (Server Component by default)
|
|
27
|
+
├── error.tsx # Error boundary ('use client' required)
|
|
28
|
+
├── not-found.tsx # 404 page
|
|
29
|
+
├── loading.tsx # Loading skeleton (shown during streaming)
|
|
30
|
+
├── globals.css # Global styles
|
|
31
|
+
├── (admin)/ # Route group (no URL segment)
|
|
32
|
+
│ └── dashboard/
|
|
33
|
+
├── _internal/ # Private folder (opted out of routing)
|
|
34
|
+
├── features/
|
|
35
|
+
│ └── [id]/
|
|
36
|
+
│ └── page.tsx # Dynamic route
|
|
37
|
+
└── api/
|
|
38
|
+
└── route.ts # API route handler
|
|
39
|
+
|
|
40
|
+
components/
|
|
41
|
+
├── ui/ # Primitives (button, card, etc.)
|
|
42
|
+
├── layout/ # Sidebar, header, footer
|
|
43
|
+
└── shared/ # Reusable across pages
|
|
44
|
+
|
|
45
|
+
lib/
|
|
46
|
+
├── data/ # Server-side data fetching functions
|
|
47
|
+
├── actions/ # Server Actions ('use server')
|
|
48
|
+
├── types/ # TypeScript interfaces
|
|
49
|
+
└── utils.ts # Formatting helpers
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Route Groups**: Use parentheses (e.g., `(admin)`) to group routes without affecting the URL path.
|
|
53
|
+
|
|
54
|
+
**Private Folders**: Prefix with `_` (e.g., `_internal`) to opt out of routing and signal implementation details.
|
|
55
|
+
|
|
56
|
+
**Feature Folders**: For large apps, group by feature (e.g., `app/dashboard/`, `app/auth/`).
|
|
57
|
+
|
|
58
|
+
## Core Patterns
|
|
59
|
+
|
|
60
|
+
### Server Components (default)
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// app/page.tsx — Server Component (no 'use client' directive)
|
|
64
|
+
export const dynamic = 'force-dynamic'; // Required when fetching from DB
|
|
65
|
+
|
|
66
|
+
import { getData } from '@/lib/data/my-data';
|
|
67
|
+
|
|
68
|
+
export default async function Page() {
|
|
69
|
+
const data = await getData(); // Runs on server only
|
|
70
|
+
|
|
71
|
+
return <div>{data.title}</div>;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Client Components (opt-in)
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// components/counter.tsx
|
|
79
|
+
'use client'; // Required for interactivity, hooks, browser APIs
|
|
80
|
+
|
|
81
|
+
import { useState } from 'react';
|
|
82
|
+
|
|
83
|
+
export function Counter() {
|
|
84
|
+
const [count, setCount] = useState(0);
|
|
85
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Server Actions (mutations)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// lib/actions/my-actions.ts
|
|
93
|
+
'use server';
|
|
94
|
+
|
|
95
|
+
import { revalidatePath } from 'next/cache';
|
|
96
|
+
|
|
97
|
+
export async function createItem(formData: FormData) {
|
|
98
|
+
const name = formData.get('name') as string;
|
|
99
|
+
// ... insert into DB
|
|
100
|
+
revalidatePath('/');
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Server-Only Data Fetching (Supabase pattern)
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// lib/supabase.ts
|
|
108
|
+
import 'server-only'; // Prevents accidental client import
|
|
109
|
+
|
|
110
|
+
import { createClient } from '@supabase/supabase-js';
|
|
111
|
+
|
|
112
|
+
export function createServerClient() {
|
|
113
|
+
return createClient(
|
|
114
|
+
process.env.SUPABASE_URL!, // No NEXT_PUBLIC_ prefix
|
|
115
|
+
process.env.SUPABASE_SECRET_KEY!, // Secret key, server-only
|
|
116
|
+
{
|
|
117
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
118
|
+
global: {
|
|
119
|
+
// CRITICAL: Bypass Next.js 14 fetch cache to prevent stale data.
|
|
120
|
+
// Without this, Supabase responses are cached by Next.js's Data Cache.
|
|
121
|
+
fetch: (input, init) =>
|
|
122
|
+
fetch(input, { ...init, cache: 'no-store' }),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Server and Client Component Integration
|
|
130
|
+
|
|
131
|
+
**Never use `next/dynamic` with `{ ssr: false }` inside a Server Component.** This is not supported and will cause a build/runtime error.
|
|
132
|
+
|
|
133
|
+
**Correct Approach:**
|
|
134
|
+
|
|
135
|
+
1. Move all client-only logic/UI into a dedicated Client Component (with `'use client'` at the top)
|
|
136
|
+
2. Import and use that Client Component directly in the Server Component
|
|
137
|
+
3. No need for `next/dynamic` — just import the Client Component normally
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Server Component
|
|
141
|
+
import DashboardNavbar from '@/components/DashboardNavbar';
|
|
142
|
+
|
|
143
|
+
export default async function DashboardPage() {
|
|
144
|
+
// ...server logic...
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<DashboardNavbar /> {/* This is a Client Component */}
|
|
148
|
+
{/* ...rest of server-rendered page... */}
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Why**: Server Components cannot use client-only features or dynamic imports with SSR disabled. Client Components can be rendered inside Server Components, but not the other way around.
|
|
155
|
+
|
|
156
|
+
## Next.js 16+ Async Request APIs
|
|
157
|
+
|
|
158
|
+
In Next.js 16, request-bound APIs are async in the App Router:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Next.js 16+ — cookies, headers, draftMode are async
|
|
162
|
+
import { cookies, headers } from 'next/headers';
|
|
163
|
+
|
|
164
|
+
export default async function Page() {
|
|
165
|
+
const cookieStore = await cookies();
|
|
166
|
+
const headersList = await headers();
|
|
167
|
+
// ...
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Route props may be Promises**: `params` and `searchParams` may be Promises in Server Components. Prefer awaiting them:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
export default async function Page({
|
|
175
|
+
params,
|
|
176
|
+
}: {
|
|
177
|
+
params: Promise<{ id: string }>;
|
|
178
|
+
}) {
|
|
179
|
+
const { id } = await params;
|
|
180
|
+
// ...
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Dynamic rendering**: Accessing request data (cookies/headers/searchParams) opts the route into dynamic behavior. Read them intentionally and isolate dynamic parts behind `Suspense` boundaries when appropriate.
|
|
185
|
+
|
|
186
|
+
## API Routes (Route Handlers)
|
|
187
|
+
|
|
188
|
+
**Location**: Place API routes in `app/api/` (e.g., `app/api/users/route.ts`).
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// app/api/users/route.ts
|
|
192
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
193
|
+
|
|
194
|
+
export async function GET(request: NextRequest) {
|
|
195
|
+
const { searchParams } = new URL(request.url);
|
|
196
|
+
const limit = searchParams.get('limit') ?? '10';
|
|
197
|
+
|
|
198
|
+
// ... fetch data
|
|
199
|
+
return NextResponse.json({ users: [] });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function POST(request: NextRequest) {
|
|
203
|
+
const body = await request.json();
|
|
204
|
+
// ... validate and create
|
|
205
|
+
return NextResponse.json({ id: 'new-id' }, { status: 201 });
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Dynamic Segments**: Use `[param]` for dynamic routes (e.g., `app/api/users/[id]/route.ts`).
|
|
210
|
+
|
|
211
|
+
**Validation**: Always validate input with libraries like `zod`.
|
|
212
|
+
|
|
213
|
+
**Performance Note**: Do NOT call your own Route Handlers from Server Components (e.g., `fetch('/api/...')`). Extract shared logic into `lib/` modules and call directly to avoid extra server hops.
|
|
214
|
+
|
|
215
|
+
## Auto-Refresh via Polling
|
|
216
|
+
|
|
217
|
+
When WebSocket-based realtime isn't available (e.g., Supabase Realtime requires paid onboarding), use polling with `router.refresh()` to re-fetch Server Component data without full page reload.
|
|
218
|
+
|
|
219
|
+
### Architecture
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
RefreshProvider (layout.tsx) — React Context tracking polling state
|
|
223
|
+
└── PollingSubscription — Zero-UI Client Component, activates polling
|
|
224
|
+
└── usePollingRefresh — Hook: setInterval → router.refresh()
|
|
225
|
+
└── ConnectionStatus — Green dot "Auto-refresh" indicator
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Hook: usePollingRefresh
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// lib/realtime/use-polling-refresh.ts
|
|
232
|
+
'use client';
|
|
233
|
+
|
|
234
|
+
import { useEffect, useRef } from 'react';
|
|
235
|
+
import { useRouter } from 'next/navigation';
|
|
236
|
+
|
|
237
|
+
const DEFAULT_INTERVAL_MS = 5000;
|
|
238
|
+
|
|
239
|
+
export function usePollingRefresh(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
240
|
+
const router = useRouter();
|
|
241
|
+
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
242
|
+
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
timerRef.current = setInterval(() => {
|
|
245
|
+
router.refresh(); // Re-fetches Server Component data
|
|
246
|
+
}, intervalMs);
|
|
247
|
+
|
|
248
|
+
return () => {
|
|
249
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
250
|
+
};
|
|
251
|
+
}, [intervalMs, router]);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Zero-UI Component (place as sibling in Server Component pages)
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// components/polling-subscription.tsx
|
|
259
|
+
'use client';
|
|
260
|
+
|
|
261
|
+
import { usePollingRefresh } from '@/lib/realtime/use-polling-refresh';
|
|
262
|
+
|
|
263
|
+
export function PollingSubscription({ intervalMs }: { intervalMs?: number }) {
|
|
264
|
+
usePollingRefresh(intervalMs);
|
|
265
|
+
return null; // Renders nothing — side effect only
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Usage in Server Component pages
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// app/page.tsx
|
|
273
|
+
import { PollingSubscription } from '@/components/polling-subscription';
|
|
274
|
+
import { getData } from '@/lib/data/my-data';
|
|
275
|
+
|
|
276
|
+
export default async function Page() {
|
|
277
|
+
const data = await getData();
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<>
|
|
281
|
+
<PollingSubscription />
|
|
282
|
+
<div>{/* ...server-rendered content auto-refreshes every 5s... */}</div>
|
|
283
|
+
</>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
> **Why sibling, not wrapper?** Wrapping an `async` Server Component return with a `'use client'` component breaks Next.js module resolution for dynamic routes (`[id]`). Always place Client Components as siblings using `<> ... </>`.
|
|
289
|
+
|
|
290
|
+
## Caching & Revalidation
|
|
291
|
+
|
|
292
|
+
### Next.js 14.x (fetch cache)
|
|
293
|
+
|
|
294
|
+
Next.js 14 patches global `fetch` with `cache: 'force-cache'` by default. Override per-fetch or per-client to prevent stale data. See "Data Cache Gotcha" below.
|
|
295
|
+
|
|
296
|
+
### Next.js 16.x (Cache Components)
|
|
297
|
+
|
|
298
|
+
Enable in `next.config.*`:
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// next.config.mjs
|
|
302
|
+
export default {
|
|
303
|
+
cacheComponents: true,
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Use the `use cache` directive to opt a component/function into caching:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
'use cache';
|
|
311
|
+
|
|
312
|
+
export async function getCachedData() {
|
|
313
|
+
// This result will be cached
|
|
314
|
+
return await fetchExpensiveData();
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Cache tagging and lifetimes**:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { cacheTag, cacheLife } from 'next/cache';
|
|
322
|
+
|
|
323
|
+
export async function getProducts() {
|
|
324
|
+
'use cache';
|
|
325
|
+
cacheTag('products'); // Associate with tag
|
|
326
|
+
cacheLife('hours'); // Set cache lifetime
|
|
327
|
+
return await fetchProducts();
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Revalidation**:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { revalidateTag } from 'next/cache';
|
|
335
|
+
|
|
336
|
+
// In a Server Action
|
|
337
|
+
export async function refreshProducts() {
|
|
338
|
+
'use server';
|
|
339
|
+
revalidateTag('products', 'max'); // stale-while-revalidate
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Prefer `revalidateTag(tag, 'max')` for most cases. Use `updateTag(...)` inside Server Actions when you need immediate consistency.
|
|
344
|
+
|
|
345
|
+
## Build Gotchas (Next.js 14+)
|
|
346
|
+
|
|
347
|
+
1. **`next.config.ts` not supported** in Next.js 14.2 — use `.mjs` or `.js`
|
|
348
|
+
|
|
349
|
+
2. **`@apply border-border` fails** unless shadcn/ui color tokens are defined in `tailwind.config.ts` (need `border: 'hsl(var(--border))'` etc.)
|
|
350
|
+
|
|
351
|
+
3. **Static prerendering fails** when pages use `import 'server-only'` Supabase client — add `export const dynamic = 'force-dynamic'` to all data-fetching pages
|
|
352
|
+
|
|
353
|
+
4. **Hydration mismatch** — `typeof window !== 'undefined'` evaluates differently server vs client. Defer browser checks to `useEffect`:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
const [isClient, setIsClient] = useState(false);
|
|
357
|
+
useEffect(() => setIsClient(true), []);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
5. **`next/dynamic` with `{ ssr: false }` in Server Components** — Not supported. Move client-only logic to a dedicated Client Component and import it directly.
|
|
361
|
+
|
|
362
|
+
## Data Cache Gotcha: Stale Supabase Data (CRITICAL)
|
|
363
|
+
|
|
364
|
+
Next.js 14 patches the global `fetch` API to add `cache: 'force-cache'` by default. Libraries that use `fetch` internally (like `@supabase/supabase-js`) will have their responses cached by Next.js's **Data Cache**, even when pages have `export const dynamic = 'force-dynamic'`.
|
|
365
|
+
|
|
366
|
+
**Symptoms**: Pages show old data despite the database having correct values. `router.refresh()` and hard refresh don't help. Direct SQL queries return correct results.
|
|
367
|
+
|
|
368
|
+
**Why `force-dynamic` isn't enough**: `force-dynamic` opts out of the **Full Route Cache** (no static prerendering) but does NOT bypass the **Data Cache** for individual `fetch` calls. The Supabase client's internal fetch calls inherit the default `cache: 'force-cache'` behavior.
|
|
369
|
+
|
|
370
|
+
**Fix**: Override the Supabase client's `fetch` to pass `cache: 'no-store'`:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// lib/supabase.ts
|
|
374
|
+
client = createClient(url, key, {
|
|
375
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
376
|
+
global: {
|
|
377
|
+
fetch: (input, init) =>
|
|
378
|
+
fetch(input, { ...init, cache: 'no-store' }),
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Layouts are especially vulnerable**: `app/layout.tsx` doesn't support `export const dynamic`. Async Server Components in layouts (e.g., a command palette that fetches features) are always subject to fetch caching unless the client itself opts out.
|
|
384
|
+
|
|
385
|
+
**See also**: database/supabase (client setup with `cache: 'no-store'`)
|
|
386
|
+
**Confidence**: 1.00 | **Validated by**: integration-test | **Source**: DASH-003
|
|
387
|
+
|
|
388
|
+
## Naming Conventions
|
|
389
|
+
|
|
390
|
+
### Files and Folders
|
|
391
|
+
|
|
392
|
+
| Element | Convention | Example |
|
|
393
|
+
|---------|------------|---------|
|
|
394
|
+
| Folders | `kebab-case` | `user-profile/` |
|
|
395
|
+
| Component files | `PascalCase` | `UserCard.tsx` |
|
|
396
|
+
| Utility/hook files | `camelCase` | `useUser.ts` |
|
|
397
|
+
| Static assets | `kebab-case` or `snake_case` | `logo-dark.svg` |
|
|
398
|
+
| Context providers | `XyzProvider` | `ThemeProvider` |
|
|
399
|
+
|
|
400
|
+
### Code
|
|
401
|
+
|
|
402
|
+
| Element | Convention | Example |
|
|
403
|
+
|---------|------------|---------|
|
|
404
|
+
| Variables/Functions | `camelCase` | `getUserData` |
|
|
405
|
+
| Types/Interfaces | `PascalCase` | `UserProfile` |
|
|
406
|
+
| Constants | `UPPER_SNAKE_CASE` | `MAX_RETRIES` |
|
|
407
|
+
|
|
408
|
+
## Component Best Practices
|
|
409
|
+
|
|
410
|
+
**When to Create a Component:**
|
|
411
|
+
- If a UI pattern is reused more than once
|
|
412
|
+
- If a section of a page is complex or self-contained
|
|
413
|
+
- If it improves readability or testability
|
|
414
|
+
|
|
415
|
+
**Props:**
|
|
416
|
+
- Use TypeScript interfaces for props
|
|
417
|
+
- Prefer explicit prop types and default values
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
interface UserCardProps {
|
|
421
|
+
user: User;
|
|
422
|
+
showAvatar?: boolean;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function UserCard({ user, showAvatar = true }: UserCardProps) {
|
|
426
|
+
// ...
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Component Location:**
|
|
431
|
+
- Place shared components in `components/`
|
|
432
|
+
- Place route-specific components inside the relevant route folder
|
|
433
|
+
|
|
434
|
+
**Testing:**
|
|
435
|
+
- Co-locate tests with components (e.g., `UserCard.test.tsx`)
|
|
436
|
+
|
|
437
|
+
## Tooling Updates (Next.js 16)
|
|
438
|
+
|
|
439
|
+
**Turbopack is the default dev bundler.** Configure via the top-level `turbopack` field in `next.config.*`:
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
// next.config.mjs
|
|
443
|
+
export default {
|
|
444
|
+
turbopack: {
|
|
445
|
+
// turbopack options
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Typed routes are stable** via `typedRoutes: true` (TypeScript required).
|
|
451
|
+
|
|
452
|
+
**ESLint**: In Next.js 16, prefer running ESLint via the ESLint CLI (not `next lint`).
|
|
453
|
+
|
|
454
|
+
**Environment Variables**: `serverRuntimeConfig` / `publicRuntimeConfig` are removed. Use environment variables directly. Note that `NEXT_PUBLIC_` variables are inlined at build time.
|
|
455
|
+
|
|
456
|
+
## Best Practices
|
|
457
|
+
|
|
458
|
+
1. **Server Components by default** — only add `'use client'` when you need hooks, event handlers, or browser APIs
|
|
459
|
+
2. **`import 'server-only'`** on any module that uses secrets — prevents accidental client bundling
|
|
460
|
+
3. **No `NEXT_PUBLIC_` for secrets** — only use the prefix for values safe to expose in the browser
|
|
461
|
+
4. **`force-dynamic` on DB pages** — prevents build-time prerendering that requires a live database
|
|
462
|
+
5. **Server Actions for mutations** — use `'use server'` functions instead of API routes for form submissions
|
|
463
|
+
6. **Parallel data fetching** — use `Promise.all()` in Server Components to fetch data concurrently
|
|
464
|
+
7. **Loading skeletons** — add `loading.tsx` per route for instant navigation feedback
|
|
465
|
+
8. **Error boundaries** — add `error.tsx` per route (must be `'use client'`)
|
|
466
|
+
9. **Virtual DOM** — Do not manipulate the real DOM. Use `useState` for state changes.
|
|
467
|
+
10. **TypeScript strict mode** — Enable `strict: true` in `tsconfig.json`
|
|
468
|
+
11. **Validate all input** — Use libraries like `zod` for API routes and Server Actions
|
|
469
|
+
12. **Use Suspense boundaries** — Isolate async data fetching for progressive loading
|
|
470
|
+
13. **Avoid large client bundles** — Keep most logic in Server Components
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-patterns
|
|
3
|
+
description: React component patterns, hooks, state management, and performance optimization
|
|
4
|
+
category: frontend
|
|
5
|
+
version: "18.x"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- nextjs-dev
|
|
8
|
+
- tailwindcss
|
|
9
|
+
- jest
|
|
10
|
+
- vitest
|
|
11
|
+
- typescript
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# React Patterns
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
React is a declarative component library for building user interfaces. This skill covers idiomatic patterns for components, hooks, state management, and performance.
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/
|
|
24
|
+
├── components/
|
|
25
|
+
│ ├── ui/ # Generic UI primitives (Button, Input, Modal)
|
|
26
|
+
│ ├── features/ # Feature-specific components
|
|
27
|
+
│ └── layouts/ # Page layouts
|
|
28
|
+
├── hooks/ # Custom hooks
|
|
29
|
+
├── context/ # React context providers
|
|
30
|
+
├── lib/ # Utility functions
|
|
31
|
+
├── types/ # Shared TypeScript types
|
|
32
|
+
└── App.tsx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Patterns
|
|
36
|
+
|
|
37
|
+
### Component Composition
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// Prefer composition over prop drilling
|
|
41
|
+
function Card({ children }: { children: React.ReactNode }) {
|
|
42
|
+
return <div className="rounded-lg border p-4">{children}</div>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Card.Header = function CardHeader({ children }: { children: React.ReactNode }) {
|
|
46
|
+
return <div className="mb-2 font-bold">{children}</div>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Card.Body = function CardBody({ children }: { children: React.ReactNode }) {
|
|
50
|
+
return <div>{children}</div>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Usage
|
|
54
|
+
<Card>
|
|
55
|
+
<Card.Header>Title</Card.Header>
|
|
56
|
+
<Card.Body>Content</Card.Body>
|
|
57
|
+
</Card>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Custom Hooks
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Extract reusable logic into hooks
|
|
64
|
+
function useAsync<T>(asyncFn: () => Promise<T>, deps: unknown[] = []) {
|
|
65
|
+
const [state, setState] = useState<{
|
|
66
|
+
data: T | null;
|
|
67
|
+
error: Error | null;
|
|
68
|
+
loading: boolean;
|
|
69
|
+
}>({ data: null, error: null, loading: true });
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
let cancelled = false;
|
|
73
|
+
setState(s => ({ ...s, loading: true }));
|
|
74
|
+
|
|
75
|
+
asyncFn()
|
|
76
|
+
.then(data => { if (!cancelled) setState({ data, error: null, loading: false }); })
|
|
77
|
+
.catch(error => { if (!cancelled) setState({ data: null, error, loading: false }); });
|
|
78
|
+
|
|
79
|
+
return () => { cancelled = true; };
|
|
80
|
+
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
|
|
81
|
+
|
|
82
|
+
return state;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### State Management
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// useReducer for complex state
|
|
90
|
+
type State = { items: Item[]; filter: string; sort: SortKey };
|
|
91
|
+
type Action =
|
|
92
|
+
| { type: 'ADD_ITEM'; payload: Item }
|
|
93
|
+
| { type: 'SET_FILTER'; payload: string }
|
|
94
|
+
| { type: 'SET_SORT'; payload: SortKey };
|
|
95
|
+
|
|
96
|
+
function reducer(state: State, action: Action): State {
|
|
97
|
+
switch (action.type) {
|
|
98
|
+
case 'ADD_ITEM':
|
|
99
|
+
return { ...state, items: [...state.items, action.payload] };
|
|
100
|
+
case 'SET_FILTER':
|
|
101
|
+
return { ...state, filter: action.payload };
|
|
102
|
+
case 'SET_SORT':
|
|
103
|
+
return { ...state, sort: action.payload };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Error Boundaries
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
class ErrorBoundary extends React.Component<
|
|
112
|
+
{ fallback: React.ReactNode; children: React.ReactNode },
|
|
113
|
+
{ hasError: boolean }
|
|
114
|
+
> {
|
|
115
|
+
state = { hasError: false };
|
|
116
|
+
|
|
117
|
+
static getDerivedStateFromError() {
|
|
118
|
+
return { hasError: true };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
122
|
+
console.error('Error boundary caught:', error, info);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
render() {
|
|
126
|
+
if (this.state.hasError) return this.props.fallback;
|
|
127
|
+
return this.props.children;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Performance
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
// Memoize expensive components
|
|
136
|
+
const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) {
|
|
137
|
+
return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Memoize expensive calculations
|
|
141
|
+
const sorted = useMemo(() => items.sort(compareFn), [items]);
|
|
142
|
+
|
|
143
|
+
// Stable callback references
|
|
144
|
+
const handleClick = useCallback((id: string) => {
|
|
145
|
+
setSelected(id);
|
|
146
|
+
}, []);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Best Practices
|
|
150
|
+
|
|
151
|
+
1. **Lift state up** only as far as needed — colocate state with the component that uses it
|
|
152
|
+
2. **Derive state** from props/existing state instead of syncing with `useEffect`
|
|
153
|
+
3. **Key lists properly** — use stable, unique IDs (not array indices)
|
|
154
|
+
4. **Avoid premature memoization** — `memo`/`useMemo`/`useCallback` only when profiling shows need
|
|
155
|
+
5. **Single responsibility** — one component = one concern
|
|
156
|
+
6. **Type events explicitly** — `React.ChangeEvent<HTMLInputElement>` over `any`
|
|
157
|
+
7. **Use fragments** — `<>...</>` to avoid unnecessary wrapper divs
|
|
158
|
+
8. **Controlled forms** — manage form state in React, not the DOM
|
|
159
|
+
|
|
160
|
+
## Gotchas
|
|
161
|
+
|
|
162
|
+
- **Stale closures** in `useEffect`/`useCallback` — check dependency arrays
|
|
163
|
+
- **Object/array identity** in deps — `useMemo` to stabilize reference equality
|
|
164
|
+
- **useEffect firing twice** in StrictMode (development) — not a bug, tests cleanup
|
|
165
|
+
- **setState is async** — use functional updates (`setCount(c => c + 1)`) when depending on previous state
|
|
166
|
+
- **Children re-rendering** — pass children as props/composition to avoid unnecessary rerenders
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: svelte-dev
|
|
3
|
+
description: Aids in Svelte development, including reactive declarations, stores, transitions, and component logic. Ideal for users building lightweight, compiler-optimized frontend apps without a virtual DOM.
|
|
4
|
+
category: frontend
|
|
5
|
+
compatible_with:
|
|
6
|
+
- tailwindcss
|
|
7
|
+
- vitest
|
|
8
|
+
- playwright
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Svelte Development Assistance
|
|
12
|
+
|
|
13
|
+
## Instructions
|
|
14
|
+
1. **Evaluate the task**: Check for component building, state management, or animation needs.
|
|
15
|
+
2. **Utilize Svelte's strengths**: Highlight compile-time optimizations and simple syntax.
|
|
16
|
+
3. **Provide responses**:
|
|
17
|
+
- Code: Output .svelte files with reactive statements and bindings.
|
|
18
|
+
- Debugging: Explain common errors like undefined variables or store subscriptions.
|
|
19
|
+
- Advanced: Cover SvelteKit for routing and server-side features.
|
|
20
|
+
4. **Leverage tools**: Use code_execution to run Svelte-compatible JS code.
|
|
21
|
+
5. **Keep explanations simple**: Use everyday analogies and reference Svelte tutorials.
|
|
22
|
+
|
|
23
|
+
## Best Practices
|
|
24
|
+
- Use writable stores for global state.
|
|
25
|
+
- Minimize external dependencies.
|
|
26
|
+
- Optimize with preload and lazy loading.
|
|
27
|
+
|
|
28
|
+
For detailed and up-to-date knowledge and examples on the latest Svelte, use Tessl.io respective tile in tessl/npm-svelte
|