@qazuor/claude-code-config 0.5.0 → 0.6.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/README.md +106 -41
- package/dist/bin.cjs +963 -84
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +963 -84
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +73 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -56
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Next.js App Router Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Next.js is a React framework with server-side rendering and App Router. This skill provides patterns for building Next.js applications.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Server Components (Default)
|
|
10
|
+
|
|
11
|
+
### Data Fetching in Server Components
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// app/items/page.tsx
|
|
15
|
+
import { getItems } from '@/lib/actions/items';
|
|
16
|
+
import { ItemList } from '@/components/features/item-list';
|
|
17
|
+
|
|
18
|
+
export default async function ItemsPage() {
|
|
19
|
+
const items = await getItems();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<main className="container py-8">
|
|
23
|
+
<h1 className="text-2xl font-bold mb-4">Items</h1>
|
|
24
|
+
<ItemList items={items} />
|
|
25
|
+
</main>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### With Search Params
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// app/items/page.tsx
|
|
34
|
+
interface PageProps {
|
|
35
|
+
searchParams: { q?: string; page?: string };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default async function ItemsPage({ searchParams }: PageProps) {
|
|
39
|
+
const query = searchParams.q || '';
|
|
40
|
+
const page = Number(searchParams.page) || 1;
|
|
41
|
+
|
|
42
|
+
const { items, totalPages } = await getItems({ query, page });
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<main>
|
|
46
|
+
<SearchBar defaultValue={query} />
|
|
47
|
+
<ItemList items={items} />
|
|
48
|
+
<Pagination currentPage={page} totalPages={totalPages} />
|
|
49
|
+
</main>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Client Components
|
|
57
|
+
|
|
58
|
+
### Interactive Components
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
'use client';
|
|
62
|
+
|
|
63
|
+
import { useState } from 'react';
|
|
64
|
+
import { Button } from '@/components/ui/button';
|
|
65
|
+
import { updateItem } from '@/lib/actions/items';
|
|
66
|
+
import { useToast } from '@/hooks/use-toast';
|
|
67
|
+
|
|
68
|
+
interface ItemCardProps {
|
|
69
|
+
item: { id: string; title: string };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ItemCard({ item }: ItemCardProps) {
|
|
73
|
+
const [isPending, setIsPending] = useState(false);
|
|
74
|
+
const { toast } = useToast();
|
|
75
|
+
|
|
76
|
+
const handleUpdate = async () => {
|
|
77
|
+
setIsPending(true);
|
|
78
|
+
try {
|
|
79
|
+
await updateItem(item.id, { title: 'Updated' });
|
|
80
|
+
toast({ title: 'Item updated' });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
toast({ title: 'Error updating item', variant: 'destructive' });
|
|
83
|
+
} finally {
|
|
84
|
+
setIsPending(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="p-4 border rounded">
|
|
90
|
+
<p>{item.title}</p>
|
|
91
|
+
<Button onClick={handleUpdate} disabled={isPending}>
|
|
92
|
+
{isPending ? 'Updating...' : 'Update'}
|
|
93
|
+
</Button>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Server Actions
|
|
102
|
+
|
|
103
|
+
### Form Action
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// lib/actions/items.ts
|
|
107
|
+
'use server';
|
|
108
|
+
|
|
109
|
+
import { revalidatePath } from 'next/cache';
|
|
110
|
+
import { redirect } from 'next/navigation';
|
|
111
|
+
import { z } from 'zod';
|
|
112
|
+
import { db } from '@/lib/db';
|
|
113
|
+
import { getSession } from '@/lib/auth';
|
|
114
|
+
|
|
115
|
+
const createItemSchema = z.object({
|
|
116
|
+
title: z.string().min(1),
|
|
117
|
+
description: z.string().optional(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export async function createItem(formData: FormData) {
|
|
121
|
+
const session = await getSession();
|
|
122
|
+
if (!session?.user) {
|
|
123
|
+
throw new Error('Unauthorized');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const data = {
|
|
127
|
+
title: formData.get('title'),
|
|
128
|
+
description: formData.get('description'),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const parsed = createItemSchema.parse(data);
|
|
132
|
+
|
|
133
|
+
const item = await db.item.create({
|
|
134
|
+
data: {
|
|
135
|
+
...parsed,
|
|
136
|
+
authorId: session.user.id,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
revalidatePath('/items');
|
|
141
|
+
redirect(`/items/${item.id}`);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Action with Return Value
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
'use server';
|
|
149
|
+
|
|
150
|
+
import { revalidatePath } from 'next/cache';
|
|
151
|
+
|
|
152
|
+
type ActionResult =
|
|
153
|
+
| { success: true; data: Item }
|
|
154
|
+
| { success: false; error: string };
|
|
155
|
+
|
|
156
|
+
export async function updateItem(
|
|
157
|
+
id: string,
|
|
158
|
+
data: Partial<Item>
|
|
159
|
+
): Promise<ActionResult> {
|
|
160
|
+
try {
|
|
161
|
+
const session = await getSession();
|
|
162
|
+
if (!session?.user) {
|
|
163
|
+
return { success: false, error: 'Unauthorized' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const item = await db.item.update({
|
|
167
|
+
where: { id, authorId: session.user.id },
|
|
168
|
+
data,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
revalidatePath('/items');
|
|
172
|
+
revalidatePath(`/items/${id}`);
|
|
173
|
+
|
|
174
|
+
return { success: true, data: item };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return { success: false, error: 'Failed to update item' };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Route Handlers
|
|
184
|
+
|
|
185
|
+
### API Route
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// app/api/items/route.ts
|
|
189
|
+
import { NextResponse } from 'next/server';
|
|
190
|
+
import { db } from '@/lib/db';
|
|
191
|
+
import { getSession } from '@/lib/auth';
|
|
192
|
+
|
|
193
|
+
export async function GET(request: Request) {
|
|
194
|
+
const { searchParams } = new URL(request.url);
|
|
195
|
+
const page = Number(searchParams.get('page')) || 1;
|
|
196
|
+
const limit = Number(searchParams.get('limit')) || 10;
|
|
197
|
+
|
|
198
|
+
const items = await db.item.findMany({
|
|
199
|
+
where: { status: 'active' },
|
|
200
|
+
take: limit,
|
|
201
|
+
skip: (page - 1) * limit,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return NextResponse.json({ data: items });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function POST(request: Request) {
|
|
208
|
+
const session = await getSession();
|
|
209
|
+
if (!session?.user) {
|
|
210
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const body = await request.json();
|
|
214
|
+
|
|
215
|
+
const item = await db.item.create({
|
|
216
|
+
data: { ...body, authorId: session.user.id },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return NextResponse.json({ data: item }, { status: 201 });
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Dynamic Route Handler
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// app/api/items/[id]/route.ts
|
|
227
|
+
import { NextResponse } from 'next/server';
|
|
228
|
+
|
|
229
|
+
interface RouteParams {
|
|
230
|
+
params: { id: string };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function GET(request: Request, { params }: RouteParams) {
|
|
234
|
+
const item = await db.item.findUnique({
|
|
235
|
+
where: { id: params.id },
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!item) {
|
|
239
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return NextResponse.json({ data: item });
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Layouts and Loading States
|
|
249
|
+
|
|
250
|
+
### Root Layout
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// app/layout.tsx
|
|
254
|
+
import { Inter } from 'next/font/google';
|
|
255
|
+
import { Providers } from '@/components/providers';
|
|
256
|
+
import { Header } from '@/components/layout/header';
|
|
257
|
+
import './globals.css';
|
|
258
|
+
|
|
259
|
+
const inter = Inter({ subsets: ['latin'] });
|
|
260
|
+
|
|
261
|
+
export const metadata = {
|
|
262
|
+
title: 'My App',
|
|
263
|
+
description: 'App description',
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default function RootLayout({
|
|
267
|
+
children,
|
|
268
|
+
}: {
|
|
269
|
+
children: React.ReactNode;
|
|
270
|
+
}) {
|
|
271
|
+
return (
|
|
272
|
+
<html lang="en">
|
|
273
|
+
<body className={inter.className}>
|
|
274
|
+
<Providers>
|
|
275
|
+
<Header />
|
|
276
|
+
{children}
|
|
277
|
+
</Providers>
|
|
278
|
+
</body>
|
|
279
|
+
</html>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Loading State
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// app/items/loading.tsx
|
|
288
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
289
|
+
|
|
290
|
+
export default function Loading() {
|
|
291
|
+
return (
|
|
292
|
+
<div className="container py-8">
|
|
293
|
+
<Skeleton className="h-8 w-48 mb-4" />
|
|
294
|
+
<div className="grid gap-4">
|
|
295
|
+
{[1, 2, 3].map((i) => (
|
|
296
|
+
<Skeleton key={i} className="h-24 w-full" />
|
|
297
|
+
))}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Error Handling
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// app/items/error.tsx
|
|
308
|
+
'use client';
|
|
309
|
+
|
|
310
|
+
import { Button } from '@/components/ui/button';
|
|
311
|
+
|
|
312
|
+
interface ErrorProps {
|
|
313
|
+
error: Error & { digest?: string };
|
|
314
|
+
reset: () => void;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export default function Error({ error, reset }: ErrorProps) {
|
|
318
|
+
return (
|
|
319
|
+
<div className="container py-8">
|
|
320
|
+
<div className="p-4 border border-red-500 rounded">
|
|
321
|
+
<h2 className="text-lg font-bold">Something went wrong!</h2>
|
|
322
|
+
<p className="text-sm text-muted-foreground">{error.message}</p>
|
|
323
|
+
<Button onClick={reset} className="mt-4">
|
|
324
|
+
Try again
|
|
325
|
+
</Button>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Caching and Revalidation
|
|
335
|
+
|
|
336
|
+
### Static Generation
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// Generate static pages at build time
|
|
340
|
+
export async function generateStaticParams() {
|
|
341
|
+
const items = await db.item.findMany({
|
|
342
|
+
select: { id: true },
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return items.map((item) => ({
|
|
346
|
+
id: item.id,
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Revalidation Options
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// Time-based revalidation
|
|
355
|
+
export const revalidate = 3600; // Revalidate every hour
|
|
356
|
+
|
|
357
|
+
// On-demand revalidation in Server Actions
|
|
358
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
359
|
+
|
|
360
|
+
export async function createItem() {
|
|
361
|
+
// ... create item
|
|
362
|
+
revalidatePath('/items');
|
|
363
|
+
revalidateTag('items');
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Fetch with Cache Options
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Cache forever (default)
|
|
371
|
+
const data = await fetch(url);
|
|
372
|
+
|
|
373
|
+
// Don't cache
|
|
374
|
+
const data = await fetch(url, { cache: 'no-store' });
|
|
375
|
+
|
|
376
|
+
// Revalidate after time
|
|
377
|
+
const data = await fetch(url, { next: { revalidate: 3600 } });
|
|
378
|
+
|
|
379
|
+
// Tag-based revalidation
|
|
380
|
+
const data = await fetch(url, { next: { tags: ['items'] } });
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Middleware
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// middleware.ts
|
|
389
|
+
import { NextResponse } from 'next/server';
|
|
390
|
+
import type { NextRequest } from 'next/server';
|
|
391
|
+
import { getToken } from 'next-auth/jwt';
|
|
392
|
+
|
|
393
|
+
export async function middleware(request: NextRequest) {
|
|
394
|
+
const token = await getToken({ req: request });
|
|
395
|
+
const isAuth = !!token;
|
|
396
|
+
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
|
|
397
|
+
|
|
398
|
+
if (isAuthPage) {
|
|
399
|
+
if (isAuth) {
|
|
400
|
+
return NextResponse.redirect(new URL('/dashboard', request.url));
|
|
401
|
+
}
|
|
402
|
+
return NextResponse.next();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!isAuth) {
|
|
406
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return NextResponse.next();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export const config = {
|
|
413
|
+
matcher: ['/dashboard/:path*', '/login'],
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Project Structure
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
app/
|
|
423
|
+
├── layout.tsx # Root layout
|
|
424
|
+
├── page.tsx # Home page
|
|
425
|
+
├── loading.tsx # Loading UI
|
|
426
|
+
├── error.tsx # Error UI
|
|
427
|
+
├── not-found.tsx # 404 page
|
|
428
|
+
├── (auth)/ # Route group (no URL impact)
|
|
429
|
+
│ ├── login/page.tsx
|
|
430
|
+
│ └── register/page.tsx
|
|
431
|
+
├── items/
|
|
432
|
+
│ ├── layout.tsx
|
|
433
|
+
│ ├── page.tsx
|
|
434
|
+
│ ├── loading.tsx
|
|
435
|
+
│ └── [id]/
|
|
436
|
+
│ ├── page.tsx
|
|
437
|
+
│ └── edit/page.tsx
|
|
438
|
+
├── api/
|
|
439
|
+
│ └── items/
|
|
440
|
+
│ └── route.ts
|
|
441
|
+
components/
|
|
442
|
+
├── ui/ # Base UI components
|
|
443
|
+
└── features/ # Feature components
|
|
444
|
+
lib/
|
|
445
|
+
├── actions/ # Server Actions
|
|
446
|
+
├── db/ # Database client
|
|
447
|
+
├── auth/ # Authentication
|
|
448
|
+
└── utils/
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Best Practices
|
|
454
|
+
|
|
455
|
+
### Good
|
|
456
|
+
|
|
457
|
+
- Use Server Components by default
|
|
458
|
+
- Use Client Components only for interactivity
|
|
459
|
+
- Use Server Actions for mutations
|
|
460
|
+
- Use loading.tsx for instant feedback
|
|
461
|
+
- Use error.tsx for graceful error handling
|
|
462
|
+
- Use generateStaticParams for static pages
|
|
463
|
+
|
|
464
|
+
### Bad
|
|
465
|
+
|
|
466
|
+
- 'use client' everywhere (loses server benefits)
|
|
467
|
+
- Client-side fetching when server fetch works
|
|
468
|
+
- No error handling
|
|
469
|
+
- Ignoring caching strategies
|
|
470
|
+
- Not using revalidation
|