omgkit 2.1.0 → 2.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/package.json +1 -1
- package/plugin/skills/databases/postgresql/SKILL.md +494 -18
- package/plugin/skills/devops/docker/SKILL.md +466 -18
- package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
- package/plugin/skills/frameworks/react/SKILL.md +1006 -32
- package/plugin/skills/languages/python/SKILL.md +489 -25
- package/plugin/skills/languages/typescript/SKILL.md +379 -30
|
@@ -1,77 +1,440 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nextjs
|
|
3
|
-
description: Next.js development
|
|
3
|
+
description: Next.js development with App Router, Server Components, API routes, and full-stack patterns
|
|
4
|
+
category: frameworks
|
|
5
|
+
triggers:
|
|
6
|
+
- nextjs
|
|
7
|
+
- next.js
|
|
8
|
+
- next
|
|
9
|
+
- app router
|
|
10
|
+
- server components
|
|
11
|
+
- rsc
|
|
4
12
|
---
|
|
5
13
|
|
|
6
|
-
# Next.js
|
|
14
|
+
# Next.js
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
Modern **Next.js development** with App Router following industry best practices. This skill covers Server Components, data fetching, API routes, middleware, authentication, and full-stack patterns.
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
## Purpose
|
|
19
|
+
|
|
20
|
+
Build production-ready Next.js applications:
|
|
21
|
+
|
|
22
|
+
- Master App Router architecture
|
|
23
|
+
- Implement Server and Client Components
|
|
24
|
+
- Handle data fetching and caching
|
|
25
|
+
- Create type-safe API routes
|
|
26
|
+
- Implement authentication and middleware
|
|
27
|
+
- Optimize performance and SEO
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
### 1. App Router Structure
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// app/layout.tsx - Root layout
|
|
35
|
+
import type { Metadata } from 'next';
|
|
36
|
+
|
|
37
|
+
export const metadata: Metadata = {
|
|
38
|
+
title: { default: 'My App', template: '%s | My App' },
|
|
39
|
+
description: 'A Next.js application',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default function RootLayout({
|
|
43
|
+
children,
|
|
44
|
+
}: {
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<html lang="en">
|
|
49
|
+
<body>
|
|
50
|
+
<Header />
|
|
51
|
+
<main>{children}</main>
|
|
52
|
+
<Footer />
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// app/page.tsx - Home page
|
|
59
|
+
import { Suspense } from 'react';
|
|
60
|
+
|
|
61
|
+
export default function HomePage() {
|
|
62
|
+
return (
|
|
63
|
+
<div className="container mx-auto py-8">
|
|
64
|
+
<h1 className="text-3xl font-bold">Featured Products</h1>
|
|
65
|
+
<Suspense fallback={<ProductListSkeleton />}>
|
|
66
|
+
<ProductList />
|
|
67
|
+
</Suspense>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// app/products/[id]/page.tsx - Dynamic route
|
|
73
|
+
import { notFound } from 'next/navigation';
|
|
74
|
+
|
|
75
|
+
interface ProductPageProps {
|
|
76
|
+
params: { id: string };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function generateMetadata({ params }: ProductPageProps) {
|
|
80
|
+
const product = await getProduct(params.id);
|
|
81
|
+
if (!product) return { title: 'Not Found' };
|
|
82
|
+
return { title: product.name, description: product.description };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default async function ProductPage({ params }: ProductPageProps) {
|
|
86
|
+
const product = await getProduct(params.id);
|
|
87
|
+
if (!product) notFound();
|
|
88
|
+
return <ProductDetails product={product} />;
|
|
16
89
|
}
|
|
17
90
|
```
|
|
18
91
|
|
|
19
|
-
### Client
|
|
20
|
-
|
|
92
|
+
### 2. Server and Client Components
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Server Component (default)
|
|
96
|
+
// app/components/product-list.tsx
|
|
97
|
+
import { getProducts } from '@/lib/products';
|
|
98
|
+
|
|
99
|
+
export async function ProductList() {
|
|
100
|
+
const products = await getProducts();
|
|
101
|
+
return (
|
|
102
|
+
<div className="grid grid-cols-3 gap-6">
|
|
103
|
+
{products.map((product) => (
|
|
104
|
+
<ProductCard key={product.id} product={product} />
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Client Component
|
|
111
|
+
// app/components/add-to-cart-button.tsx
|
|
21
112
|
'use client';
|
|
22
113
|
|
|
23
|
-
import { useState } from 'react';
|
|
114
|
+
import { useState, useTransition } from 'react';
|
|
115
|
+
import { addToCart } from '@/lib/actions';
|
|
116
|
+
|
|
117
|
+
export function AddToCartButton({ productId }: { productId: string }) {
|
|
118
|
+
const [isPending, startTransition] = useTransition();
|
|
119
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
120
|
+
|
|
121
|
+
const handleClick = () => {
|
|
122
|
+
startTransition(async () => {
|
|
123
|
+
const result = await addToCart(productId);
|
|
124
|
+
setMessage(result.message);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
24
127
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
128
|
+
return (
|
|
129
|
+
<button onClick={handleClick} disabled={isPending}>
|
|
130
|
+
{isPending ? 'Adding...' : 'Add to Cart'}
|
|
131
|
+
</button>
|
|
132
|
+
);
|
|
28
133
|
}
|
|
29
134
|
```
|
|
30
135
|
|
|
31
|
-
### Server
|
|
32
|
-
|
|
136
|
+
### 3. Server Actions
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// app/lib/actions.ts
|
|
33
140
|
'use server';
|
|
34
141
|
|
|
35
|
-
import { revalidatePath } from 'next/cache';
|
|
142
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
143
|
+
import { redirect } from 'next/navigation';
|
|
144
|
+
import { z } from 'zod';
|
|
145
|
+
import { auth } from '@/lib/auth';
|
|
146
|
+
|
|
147
|
+
const createProductSchema = z.object({
|
|
148
|
+
name: z.string().min(1),
|
|
149
|
+
price: z.number().positive(),
|
|
150
|
+
description: z.string().optional(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export async function createProduct(formData: FormData) {
|
|
154
|
+
const session = await auth();
|
|
155
|
+
if (!session?.user?.id) throw new Error('Unauthorized');
|
|
156
|
+
|
|
157
|
+
const validatedFields = createProductSchema.safeParse({
|
|
158
|
+
name: formData.get('name'),
|
|
159
|
+
price: Number(formData.get('price')),
|
|
160
|
+
description: formData.get('description'),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!validatedFields.success) {
|
|
164
|
+
return { success: false, errors: validatedFields.error.flatten().fieldErrors };
|
|
165
|
+
}
|
|
36
166
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
email: formData.get('email'),
|
|
167
|
+
const product = await db.product.create({
|
|
168
|
+
data: { ...validatedFields.data, userId: session.user.id },
|
|
40
169
|
});
|
|
41
|
-
|
|
170
|
+
|
|
171
|
+
revalidatePath('/products');
|
|
172
|
+
redirect(`/products/${product.id}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function addToCart(productId: string) {
|
|
176
|
+
const session = await auth();
|
|
177
|
+
if (!session?.user?.id) {
|
|
178
|
+
return { success: false, message: 'Please sign in' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await db.cartItem.upsert({
|
|
182
|
+
where: { userId_productId: { userId: session.user.id, productId } },
|
|
183
|
+
update: { quantity: { increment: 1 } },
|
|
184
|
+
create: { userId: session.user.id, productId, quantity: 1 },
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
revalidatePath('/cart');
|
|
188
|
+
return { success: true, message: 'Added to cart!' };
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 4. Data Fetching and Caching
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// Cached data fetching
|
|
196
|
+
import { unstable_cache } from 'next/cache';
|
|
197
|
+
|
|
198
|
+
export const getProducts = unstable_cache(
|
|
199
|
+
async () => {
|
|
200
|
+
return db.product.findMany({
|
|
201
|
+
include: { category: true },
|
|
202
|
+
orderBy: { createdAt: 'desc' },
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
['products'],
|
|
206
|
+
{ tags: ['products'], revalidate: 3600 }
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Fetch with built-in caching
|
|
210
|
+
async function getUser(id: string) {
|
|
211
|
+
const res = await fetch(`https://api.example.com/users/${id}`, {
|
|
212
|
+
next: { revalidate: 3600 },
|
|
213
|
+
});
|
|
214
|
+
return res.json();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// No caching for dynamic data
|
|
218
|
+
async function getCurrentPrice(symbol: string) {
|
|
219
|
+
const res = await fetch(`https://api.example.com/stocks/${symbol}`, {
|
|
220
|
+
cache: 'no-store',
|
|
221
|
+
});
|
|
222
|
+
return res.json();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Parallel data fetching
|
|
226
|
+
export async function getProductPageData(id: string) {
|
|
227
|
+
const [product, relatedProducts, reviews] = await Promise.all([
|
|
228
|
+
getProduct(id),
|
|
229
|
+
getRelatedProducts(id),
|
|
230
|
+
getProductReviews(id),
|
|
231
|
+
]);
|
|
232
|
+
return { product, relatedProducts, reviews };
|
|
42
233
|
}
|
|
43
234
|
```
|
|
44
235
|
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
236
|
+
### 5. API Routes
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// app/api/products/route.ts
|
|
240
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
241
|
+
import { z } from 'zod';
|
|
242
|
+
|
|
243
|
+
export async function GET(request: NextRequest) {
|
|
244
|
+
const searchParams = request.nextUrl.searchParams;
|
|
245
|
+
const page = Number(searchParams.get('page') || '1');
|
|
246
|
+
const limit = Number(searchParams.get('limit') || '10');
|
|
247
|
+
|
|
248
|
+
const [products, total] = await Promise.all([
|
|
249
|
+
db.product.findMany({ skip: (page - 1) * limit, take: limit }),
|
|
250
|
+
db.product.count(),
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
return NextResponse.json({
|
|
254
|
+
data: products,
|
|
255
|
+
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
|
|
256
|
+
});
|
|
51
257
|
}
|
|
52
258
|
|
|
53
|
-
export async function POST(request:
|
|
259
|
+
export async function POST(request: NextRequest) {
|
|
260
|
+
const session = await auth();
|
|
261
|
+
if (!session?.user?.id) {
|
|
262
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
263
|
+
}
|
|
264
|
+
|
|
54
265
|
const body = await request.json();
|
|
55
|
-
const
|
|
56
|
-
|
|
266
|
+
const product = await db.product.create({
|
|
267
|
+
data: { ...body, userId: session.user.id },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return NextResponse.json(product, { status: 201 });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// app/api/products/[id]/route.ts
|
|
274
|
+
export async function GET(
|
|
275
|
+
request: NextRequest,
|
|
276
|
+
{ params }: { params: { id: string } }
|
|
277
|
+
) {
|
|
278
|
+
const product = await db.product.findUnique({ where: { id: params.id } });
|
|
279
|
+
if (!product) {
|
|
280
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
281
|
+
}
|
|
282
|
+
return NextResponse.json(product);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 6. Middleware
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// middleware.ts
|
|
290
|
+
import { NextResponse } from 'next/server';
|
|
291
|
+
import type { NextRequest } from 'next/server';
|
|
292
|
+
import { auth } from '@/lib/auth';
|
|
293
|
+
|
|
294
|
+
export async function middleware(request: NextRequest) {
|
|
295
|
+
const { pathname } = request.nextUrl;
|
|
296
|
+
|
|
297
|
+
// Public routes
|
|
298
|
+
const publicRoutes = ['/', '/login', '/register'];
|
|
299
|
+
if (publicRoutes.some((route) => pathname.startsWith(route))) {
|
|
300
|
+
return NextResponse.next();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check authentication
|
|
304
|
+
const session = await auth();
|
|
305
|
+
if (!session) {
|
|
306
|
+
const loginUrl = new URL('/login', request.url);
|
|
307
|
+
loginUrl.searchParams.set('callbackUrl', pathname);
|
|
308
|
+
return NextResponse.redirect(loginUrl);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Admin-only routes
|
|
312
|
+
if (pathname.startsWith('/admin') && session.user.role !== 'admin') {
|
|
313
|
+
return NextResponse.redirect(new URL('/unauthorized', request.url));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return NextResponse.next();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const config = {
|
|
320
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 7. Error Handling
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// app/error.tsx
|
|
328
|
+
'use client';
|
|
329
|
+
|
|
330
|
+
export default function Error({
|
|
331
|
+
error,
|
|
332
|
+
reset,
|
|
333
|
+
}: {
|
|
334
|
+
error: Error;
|
|
335
|
+
reset: () => void;
|
|
336
|
+
}) {
|
|
337
|
+
return (
|
|
338
|
+
<div className="flex flex-col items-center justify-center min-h-screen">
|
|
339
|
+
<h2 className="text-2xl font-bold">Something went wrong!</h2>
|
|
340
|
+
<button onClick={reset}>Try again</button>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// app/not-found.tsx
|
|
346
|
+
import Link from 'next/link';
|
|
347
|
+
|
|
348
|
+
export default function NotFound() {
|
|
349
|
+
return (
|
|
350
|
+
<div className="text-center py-12">
|
|
351
|
+
<h2 className="text-2xl font-bold">Page Not Found</h2>
|
|
352
|
+
<Link href="/">Return Home</Link>
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
57
355
|
}
|
|
58
356
|
```
|
|
59
357
|
|
|
60
|
-
|
|
358
|
+
### 8. Performance Optimization
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// Image optimization
|
|
362
|
+
import Image from 'next/image';
|
|
363
|
+
|
|
364
|
+
export function OptimizedImage() {
|
|
365
|
+
return (
|
|
366
|
+
<Image
|
|
367
|
+
src="/hero.jpg"
|
|
368
|
+
alt="Hero"
|
|
369
|
+
width={1200}
|
|
370
|
+
height={600}
|
|
371
|
+
priority
|
|
372
|
+
placeholder="blur"
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Dynamic imports
|
|
378
|
+
import dynamic from 'next/dynamic';
|
|
379
|
+
|
|
380
|
+
const DynamicChart = dynamic(() => import('./Chart'), {
|
|
381
|
+
loading: () => <p>Loading...</p>,
|
|
382
|
+
ssr: false,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Static generation
|
|
386
|
+
export async function generateStaticParams() {
|
|
387
|
+
const products = await getProducts();
|
|
388
|
+
return products.map((product) => ({ id: product.id }));
|
|
389
|
+
}
|
|
61
390
|
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
391
|
+
|
|
392
|
+
## Use Cases
|
|
393
|
+
|
|
394
|
+
### E-commerce Product Page
|
|
395
|
+
```typescript
|
|
396
|
+
export default async function ProductPage({ params, searchParams }) {
|
|
397
|
+
const product = await getProductBySlug(params.slug);
|
|
398
|
+
if (!product) notFound();
|
|
399
|
+
|
|
400
|
+
const selectedVariant = product.variants.find(
|
|
401
|
+
(v) => v.id === searchParams.variant
|
|
402
|
+
) || product.variants[0];
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div className="grid md:grid-cols-2 gap-8">
|
|
406
|
+
<ProductGallery images={product.images} />
|
|
407
|
+
<div>
|
|
408
|
+
<h1>{product.name}</h1>
|
|
409
|
+
<p>${selectedVariant.price}</p>
|
|
410
|
+
<VariantSelector variants={product.variants} />
|
|
411
|
+
<AddToCartButton productId={product.id} />
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
70
416
|
```
|
|
71
417
|
|
|
72
418
|
## Best Practices
|
|
73
|
-
|
|
74
|
-
|
|
419
|
+
|
|
420
|
+
### Do's
|
|
421
|
+
- Use Server Components by default
|
|
422
|
+
- Colocate data fetching with components
|
|
423
|
+
- Implement loading and error states
|
|
75
424
|
- Use Server Actions for mutations
|
|
76
|
-
-
|
|
77
|
-
-
|
|
425
|
+
- Cache data appropriately
|
|
426
|
+
- Optimize images with next/image
|
|
427
|
+
|
|
428
|
+
### Don'ts
|
|
429
|
+
- Don't use 'use client' unnecessarily
|
|
430
|
+
- Don't fetch data in Client Components
|
|
431
|
+
- Don't ignore TypeScript errors
|
|
432
|
+
- Don't skip error boundaries
|
|
433
|
+
- Don't hardcode environment variables
|
|
434
|
+
|
|
435
|
+
## References
|
|
436
|
+
|
|
437
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
438
|
+
- [App Router](https://nextjs.org/docs/app)
|
|
439
|
+
- [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions)
|
|
440
|
+
- [NextAuth.js](https://next-auth.js.org)
|