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.
@@ -1,77 +1,440 @@
1
1
  ---
2
2
  name: nextjs
3
- description: Next.js development. Use for Next.js projects, App Router, Server Components, Server Actions.
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 Skill
14
+ # Next.js
7
15
 
8
- ## App Router
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
- ### Server Component (default)
11
- ```tsx
12
- // app/users/page.tsx
13
- async function UsersPage() {
14
- const users = await fetch('/api/users').then(r => r.json());
15
- return <UserList users={users} />;
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 Component
20
- ```tsx
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
- export function Counter() {
26
- const [count, setCount] = useState(0);
27
- return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
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 Action
32
- ```tsx
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
- export async function createUser(formData: FormData) {
38
- await db.users.create({
39
- email: formData.get('email'),
167
+ const product = await db.product.create({
168
+ data: { ...validatedFields.data, userId: session.user.id },
40
169
  });
41
- revalidatePath('/users');
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
- ### Route Handler
46
- ```tsx
47
- // app/api/users/route.ts
48
- export async function GET() {
49
- const users = await db.users.findMany();
50
- return Response.json(users);
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: 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 user = await db.users.create(body);
56
- return Response.json(user, { status: 201 });
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
- ## File Structure
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
- app/
63
- ├── layout.tsx
64
- ├── page.tsx
65
- ├── users/
66
- │ ├── page.tsx
67
- │ └── [id]/page.tsx
68
- └── api/
69
- └── users/route.ts
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
- - Default to Server Components
74
- - Use 'use client' only when needed
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
- - Use loading.tsx for loading states
77
- - Use error.tsx for error handling
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)