Loading...
Loading...
Provides comprehensive code review capability for Next.js applications, validates Server Components, Client Components, Server Actions, caching strategies, metadata, API routes, middleware, and performance patterns. Use when reviewing Next.js App Router code changes, before merging pull requests, after implementing new features, or for architecture validation. Triggers on "review Next.js code", "Next.js code review", "check my Next.js app".
npx skill4agent add giuseppe-trisciuoglio/developer-kit nextjs-code-reviewtypescript-software-architect-reviewglobpage.tsxlayout.tsxloading.tsxerror.tsxroute.tsmiddleware.ts'use client'cacherevalidatePromise.allgenerateStaticParamsrevalidatePathrevalidateTaguseOptimisticgenerateMetadatarobots.txtsitemap.xml// ❌ Bad: Entire page marked as client when only a button needs interactivity
'use client';
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`/api/products/${params.id}`);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<button onClick={() => addToCart(product.id)}>Add to Cart</button>
</div>
);
}
// ✅ Good: Server Component with isolated Client Component
// app/products/[id]/page.tsx (Server Component)
import { AddToCartButton } from './add-to-cart-button';
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<AddToCartButton productId={product.id} />
</div>
);
}
// app/products/[id]/add-to-cart-button.tsx (Client Component)
'use client';
export function AddToCartButton({ productId }: { productId: string }) {
return <button onClick={() => addToCart(productId)}>Add to Cart</button>;
}// ❌ Bad: Sequential data fetching creates waterfall
export default async function DashboardPage() {
const user = await getUser();
const orders = await getOrders(user.id);
const analytics = await getAnalytics(user.id);
return <Dashboard user={user} orders={orders} analytics={analytics} />;
}
// ✅ Good: Parallel data fetching with proper Suspense boundaries
export default async function DashboardPage() {
const user = await getUser();
const [orders, analytics] = await Promise.all([
getOrders(user.id),
getAnalytics(user.id),
]);
return <Dashboard user={user} orders={orders} analytics={analytics} />;
}
// ✅ Even better: Streaming with Suspense for independent sections
export default async function DashboardPage() {
const user = await getUser();
return (
<div>
<UserHeader user={user} />
<Suspense fallback={<OrdersSkeleton />}>
<OrdersSection userId={user.id} />
</Suspense>
<Suspense fallback={<AnalyticsSkeleton />}>
<AnalyticsSection userId={user.id} />
</Suspense>
</div>
);
}// ❌ Bad: Server Action without validation or authorization
'use server';
export async function deleteUser(id: string) {
await db.user.delete({ where: { id } });
}
// ✅ Good: Server Action with validation, authorization, and error handling
'use server';
import { z } from 'zod';
import { auth } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
const deleteUserSchema = z.object({ id: z.string().uuid() });
export async function deleteUser(rawData: { id: string }) {
const session = await auth();
if (!session || session.user.role !== 'admin') {
throw new Error('Unauthorized');
}
const { id } = deleteUserSchema.parse(rawData);
await db.user.delete({ where: { id } });
revalidatePath('/admin/users');
}// ❌ Bad: No cache control, fetches on every request
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return <PostList posts={posts} />;
}
// ✅ Good: Explicit caching with time-based revalidation
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600, tags: ['blog-posts'] },
}).then(r => r.json());
return <PostList posts={posts} />;
}
// Revalidation in Server Action
'use server';
export async function publishPost(data: FormData) {
await db.post.create({ data: parseFormData(data) });
revalidateTag('blog-posts');
}// ❌ Bad: Middleware runs on all routes including static assets
import { NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Missing config.matcher
// ✅ Good: Scoped middleware with proper matcher
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};'use client'Promise.allrevalidatePathrevalidateTagconfig.matchergenerateMetadatagenerateStaticParamsserver-onlyreferences/references/app-router-patterns.mdreferences/server-components.mdreferences/performance.md