
Nextjs Code Review
- 1.6k installs
- 311 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit
nextjs-code-review is an agent skill that provides comprehensive code review capability for next.js applications, validates server components, client components, server actions, caching strategies, metadata, api routes,
About
nextjs-code-review is an agent skill from giuseppe-trisciuoglio/developer-kit that provides comprehensive code review capability for next.js applications, validates server components, client components, server actions, caching strategies, metadata, api routes, middleware, and perfor. # Next.js Code Review ## Overview Evaluates Next.js App Router code against best practices for Server Components, Client Components, Server Actions, caching strategies, and production-readiness criteria. Produces actionable findings categorized by severity with concrete code examples. Delegates to `typescript-software-architect-review` agent for Developers invoke nextjs-code-review during build/backend work for backend & apis tasks. The skill documents triggers, prerequisites, and step-by-step workflows grounded in SKILL.md. Compatible with Claude Code, Cursor, and Codex agent runtimes that load marketplace skills. Review the Security Audits panel on this listing before installing in production environments.
- Reviewing Next.js pages, layouts, and route segments before merging
- Validating Server Component vs Client Component boundaries
- Checking Server Actions for security and correctness
- Reviewing data fetching patterns (fetch, cache, revalidation)
- Evaluating caching strategies (static generation, ISR, dynamic rendering)
Nextjs Code Review by the numbers
- 1,557 all-time installs (skills.sh)
- +65 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #309 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
nextjs-code-review capabilities & compatibility
- Capabilities
- reviewing next.js pages, layouts, and route segm · validating server component vs client component · checking server actions for security and correct · reviewing data fetching patterns (fetch, cache, · evaluating caching strategies (static generation
- Use cases
- orchestration
What nextjs-code-review says it does
- Reviewing Next.js pages, layouts, and route segments before merging
- Validating Server Component vs Client Component boundaries
- Checking Server Actions for security and correctness
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-code-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 311 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit ↗ |
What it does
Provides comprehensive code review capability for Next.js applications, validates Server Components, Client Components, Server Actions, caching strategies, metadata, API routes, middleware, and perfor
Who is it for?
Developers working on backend & apis during build tasks.
Skip if: Tasks outside Backend & APIs scope described in SKILL.md.
When should I use this skill?
Provides comprehensive code review capability for Next.js applications, validates Server Components, Client Components, Server Actions, caching strategies, metadata, API routes, middleware, and perfor
What you get
Completed backend & apis workflow aligned with SKILL.md steps.
- Code review comments
- Pattern correction recommendations
- Route architecture fix list
Files
Next.js Code Review
Overview
Evaluates Next.js App Router code against best practices for Server Components, Client Components, Server Actions, caching strategies, and production-readiness criteria. Produces actionable findings categorized by severity with concrete code examples. Delegates to typescript-software-architect-review agent for architectural analysis.
When to Use
- Reviewing Next.js pages, layouts, and route segments before merging
- Validating Server Component vs Client Component boundaries
- Checking Server Actions for security and correctness
- Reviewing data fetching patterns (fetch, cache, revalidation)
- Evaluating caching strategies (static generation, ISR, dynamic rendering)
- Assessing middleware implementations (authentication, redirects, rewrites)
- Reviewing API route handlers for proper request/response handling
- Validating metadata configuration for SEO
- Checking loading, error, and not-found page implementations
- After implementing new Next.js features or migrating from Pages Router
Instructions
1. Identify Scope: Determine which Next.js route segments and components are under review. Use glob to discover page.tsx, layout.tsx, loading.tsx, error.tsx, route.ts, and middleware.ts files.
2. Analyze Component Boundaries: Verify proper Server Component / Client Component separation. Check that 'use client' is placed only where necessary and as deep in the component tree as possible. Ensure Server Components don't import client-only modules.
3. Review Data Fetching: Validate fetch patterns — check for proper cache and revalidate options, parallel data fetching with Promise.all, and avoidance of request waterfalls. Verify that server-side data fetching doesn't expose sensitive data to the client.
4. Evaluate Caching Strategy: Review static vs dynamic rendering decisions. Check generateStaticParams usage for static generation, revalidatePath/revalidateTag for on-demand revalidation, and proper cache headers for API routes.
5. Assess Server Actions: Review form actions for proper validation (both client and server-side), error handling, optimistic updates with useOptimistic, and security (ensure actions don't expose sensitive operations without authorization).
6. Check Middleware: Review middleware for proper request matching, authentication/authorization logic, response modification, and performance impact. Verify it runs only on necessary routes.
7. Review Metadata & SEO: Check generateMetadata functions, Open Graph tags, structured data, robots.txt, and sitemap.xml configurations. Verify dynamic metadata is properly implemented for pages with variable content.
8. Validate Findings: Before finalizing, verify each issue by checking the actual code context. Confirm the pattern violation exists, ensure the suggested fix is applicable to the codebase, and remove any false positives.
9. Produce Review Report: Generate a structured report with severity-classified findings (Critical, Warning, Suggestion), positive observations, and prioritized recommendations with code examples.
Examples
Example 1: Server/Client Component Boundaries
// ❌ 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>;
}Example 2: Data Fetching Patterns
// ❌ 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>
);
}Example 3: Server Actions Security
// ❌ 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');
}Example 4: Caching and Revalidation
// ❌ 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');
}Example 5: Middleware Review
// ❌ 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*'],
};Review Output Format
Structure all code review findings as follows:
1. Summary
Brief overview with an overall quality score (1-10) and key observations.
2. Critical Issues (Must Fix)
Issues causing security vulnerabilities, data exposure, or broken functionality.
3. Warnings (Should Fix)
Issues that violate best practices, cause performance problems, or reduce maintainability.
4. Suggestions (Consider Improving)
Improvements for code organization, performance, or developer experience.
5. Positive Observations
Well-implemented patterns and good practices to acknowledge.
6. Recommendations
Prioritized next steps with code examples for the most impactful improvements.
Best Practices
- Keep
'use client'boundaries as deep in the tree as possible - Fetch data in Server Components — avoid client-side fetching for initial data
- Use parallel data fetching (
Promise.all) to avoid request waterfalls - Implement proper loading, error, and not-found states for every route segment
- Validate all Server Action inputs with Zod or similar libraries
- Use
revalidatePath/revalidateTaginstead of time-based revalidation when possible - Scope middleware to specific routes with
config.matcher - Implement
generateMetadatafor dynamic pages with variable content - Use
generateStaticParamsfor static pages with known parameters - Avoid importing server-only code in Client Components — use the
server-onlypackage
Constraints and Warnings
- This skill targets Next.js App Router — Pages Router patterns may differ significantly
- Respect the project's Next.js version — some features are version-specific
- Do not suggest migrating from Pages Router to App Router unless explicitly requested
- Caching behavior differs between development and production — validate in production builds
- Server Actions must never expose sensitive operations without proper authentication checks
- Focus on high-confidence issues — avoid false positives on style preferences
References
See the references/ directory for detailed review checklists and pattern documentation:
references/app-router-patterns.md— App Router best practices and patternsreferences/server-components.md— Server Component and Client Component boundary guidereferences/performance.md— Next.js performance optimization checklist
Next.js App Router Patterns
Route Segment Patterns
Layouts
Layouts wrap child segments and persist across navigations. Use for shared UI (headers, sidebars, navigation).
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1 p-6">{children}</main>
</div>
);
}Loading States
Use loading.tsx for instant loading UI with Suspense boundaries.
// app/dashboard/loading.tsx
export default function DashboardLoading() {
return (
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
<div className="h-64 bg-gray-200 rounded" />
</div>
);
}Error Boundaries
Use error.tsx for graceful error handling with recovery options.
// app/dashboard/error.tsx
'use client';
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}Not Found Pages
Use not-found.tsx for custom 404 pages.
// app/dashboard/[id]/not-found.tsx
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>The requested resource could not be found.</p>
</div>
);
}Data Fetching Patterns
Server Component Data Fetching
Fetch data directly in Server Components — no useEffect needed.
// app/products/page.tsx (Server Component)
async function getProducts(): Promise<Product[]> {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
});
if (!res.ok) throw new Error('Failed to fetch products');
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return <ProductList products={products} />;
}Parallel Data Fetching
Avoid waterfalls by fetching independent data in parallel.
export default async function DashboardPage() {
const [stats, recentOrders, notifications] = await Promise.all([
getStats(),
getRecentOrders(),
getNotifications(),
]);
return (
<div>
<StatsCards stats={stats} />
<RecentOrdersTable orders={recentOrders} />
<NotificationsList notifications={notifications} />
</div>
);
}Streaming with Suspense
Stream independent sections for faster perceived performance.
export default async function DashboardPage() {
return (
<div>
<Suspense fallback={<StatsSkeleton />}>
<StatsSection />
</Suspense>
<Suspense fallback={<OrdersSkeleton />}>
<OrdersSection />
</Suspense>
</div>
);
}
async function StatsSection() {
const stats = await getStats(); // Can stream independently
return <StatsCards stats={stats} />;
}Server Actions Patterns
Form Handling with Server Actions
// app/contacts/actions.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
const contactSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
message: z.string().min(10).max(1000),
});
export async function submitContact(formData: FormData) {
const result = contactSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
});
if (!result.success) {
return { error: result.error.flatten() };
}
await db.contact.create({ data: result.data });
revalidatePath('/contacts');
return { success: true };
}Optimistic Updates
'use client';
import { useOptimistic } from 'react';
export function TodoList({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, newTodo]
);
async function addTodo(formData: FormData) {
const title = formData.get('title') as string;
addOptimistic({ id: 'temp', title, completed: false });
await createTodo(formData);
}
return (
<form action={addTodo}>
<input name="title" />
<button type="submit">Add</button>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</form>
);
}Route Handler Patterns
API Route with Proper Validation
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
export async function POST(request: NextRequest) {
const body = await request.json();
const result = createUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten() },
{ status: 400 }
);
}
const user = await createUser(result.data);
return NextResponse.json(user, { status: 201 });
}Metadata Patterns
Dynamic Metadata
// app/products/[id]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
const product = await getProduct(id);
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
images: [{ url: product.imageUrl }],
},
};
}Caching Patterns
Static Generation with generateStaticParams
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id,
}));
}On-Demand Revalidation with Tags
// Data fetching with cache tags
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { tags: [`product-${id}`] },
});
return res.json();
}
// Server Action to revalidate
'use server';
import { revalidateTag } from 'next/cache';
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data });
revalidateTag(`product-${id}`);
}Next.js Performance Optimization Checklist
Core Web Vitals
Largest Contentful Paint (LCP)
- [ ] Hero images use
<Image>component withpriorityprop - [ ] Above-the-fold content loads without JavaScript dependency
- [ ] Fonts preloaded with
next/font(no FOIT/FOUT) - [ ] Server Components used for initial content rendering
- [ ] No client-side data fetching for primary content
First Input Delay (FID) / Interaction to Next Paint (INP)
- [ ]
'use client'boundaries minimize client JavaScript - [ ] Heavy computations offloaded to Web Workers or server
- [ ] Event handlers don't block the main thread
- [ ] Third-party scripts loaded with
next/scriptstrategy
Cumulative Layout Shift (CLS)
- [ ] Images have explicit
widthandheight(orfillwith container) - [ ] Skeleton loaders match final layout dimensions
- [ ] Fonts don't cause layout shift (use
next/font) - [ ] Dynamic content has reserved space
Data Fetching Performance
Avoid Request Waterfalls
// ❌ Sequential — slow
const user = await getUser();
const orders = await getOrders(user.id); // waits for user
const reviews = await getReviews(user.id); // waits for orders
// ✅ Parallel — fast
const user = await getUser();
const [orders, reviews] = await Promise.all([
getOrders(user.id),
getReviews(user.id),
]);Streaming with Suspense
- [ ] Independent data sections wrapped in
<Suspense>boundaries - [ ] Each Suspense boundary has a meaningful loading fallback
- [ ] Critical content renders immediately without Suspense
Caching Strategy
- [ ] Static pages use
generateStaticParamsfor build-time generation - [ ] Frequently accessed data has appropriate
revalidateintervals - [ ] Cache tags used for granular on-demand revalidation
- [ ] Dynamic rendering only for truly dynamic content (user-specific, real-time)
Image Optimization
Using next/image
- [ ] All images use
<Image>component (not<img>) - [ ] Above-the-fold images have
priority={true} - [ ] Images specify
width/heightor usefillwith sized container - [ ] Appropriate
sizesprop for responsive images - [ ] Remote image domains configured in
next.config.js
// ✅ Optimized image usage
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero banner"
width={1200}
height={600}
priority // Above the fold
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>Bundle Optimization
Code Splitting
- [ ] Dynamic imports for heavy components (
next/dynamic) - [ ] Route-based code splitting (automatic with App Router)
- [ ] Large libraries loaded conditionally
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('@/components/chart'), {
loading: () => <ChartSkeleton />,
ssr: false, // Client-only if uses browser APIs
});Tree Shaking
- [ ] Named imports instead of default imports for large libraries
- [ ] No barrel file re-exports that pull in entire modules
- [ ] Unused dependencies removed from
package.json
// ❌ Imports entire library
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ Named import — tree-shakeable
import debounce from 'lodash/debounce';
debounce(fn, 300);Font Optimization
Using next/font
- [ ] All fonts loaded through
next/font/googleornext/font/local - [ ] Font subsets specified to reduce file size
- [ ]
display: 'swap'for visible text during load
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
export default function RootLayout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
);
}Third-Party Script Optimization
Using next/script
- [ ] Analytics scripts use
strategy="afterInteractive"or"lazyOnload" - [ ] No render-blocking third-party scripts
- [ ] Critical scripts use
strategy="beforeInteractive"only when necessary
import Script from 'next/script';
<Script
src="https://analytics.example.com/script.js"
strategy="lazyOnload" // Loads after page is idle
/>Server-Side Performance
Database Queries
- [ ] Queries select only needed fields (no
SELECT *) - [ ] Pagination implemented for list endpoints
- [ ] Database connections pooled (PgBouncer, connection limits)
- [ ] No N+1 queries (use eager loading or batching)
API Route Performance
- [ ] Response headers include appropriate cache control
- [ ] Large responses use streaming
- [ ] API routes validate input early to fail fast
- [ ] Background jobs for long-running operations
Middleware Performance
- [ ] Middleware runs only on necessary routes (use
matcher) - [ ] Middleware logic is fast — no heavy computation
- [ ] No database queries in middleware (use Edge-compatible alternatives)
- [ ] Proper short-circuiting for early returns
Monitoring and Measurement
- [ ] Core Web Vitals tracked in production (Vercel Analytics, web-vitals library)
- [ ] Bundle size monitored in CI (
@next/bundle-analyzer) - [ ] Performance budgets defined for key pages
- [ ] Lighthouse CI running on pull requests
Server Components and Client Components Guide
Component Boundary Rules
When to Use Server Components (Default)
Server Components are the default in Next.js App Router. Use them when:
- Fetching data from databases or APIs
- Accessing server-side resources (file system, environment variables)
- Rendering static content that doesn't need interactivity
- Keeping sensitive logic (API keys, database queries) on the server
- Reducing client-side JavaScript bundle size
When to Use Client Components ('use client')
Add 'use client' directive only when the component needs:
- Event handlers (
onClick,onChange,onSubmit) - React hooks (
useState,useEffect,useRef,useContext) - Browser-only APIs (
window,document,localStorage) - Third-party libraries that use React hooks or browser APIs
Boundary Placement Strategy
Push 'use client' Down the Tree
The 'use client' directive creates a boundary — everything imported by a Client Component becomes client-side code. Place boundaries as deep as possible.
// ❌ Bad: Entire page is a Client Component
'use client';
export default function ProductPage({ params }) {
const [quantity, setQuantity] = useState(1);
const product = useProductQuery(params.id); // Client-side fetch
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<QuantitySelector value={quantity} onChange={setQuantity} />
</div>
);
}
// ✅ Good: Only interactive part is a Client Component
// page.tsx (Server Component)
export default async function ProductPage({ params }) {
const { id } = await params;
const product = await getProduct(id); // Server-side fetch
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<QuantitySelector productId={product.id} />
</div>
);
}
// quantity-selector.tsx (Client Component)
'use client';
export function QuantitySelector({ productId }: { productId: string }) {
const [quantity, setQuantity] = useState(1);
return <input type="number" value={quantity} onChange={e => setQuantity(+e.target.value)} />;
}Data Passing Patterns
Server to Client: Props
Pass serializable data from Server Components to Client Components via props.
// Server Component
export default async function Page() {
const user = await getUser(); // Fetched on server
return <UserCard user={user} />; // Passed as prop
}
// Client Component
'use client';
function UserCard({ user }: { user: User }) {
const [editing, setEditing] = useState(false);
return <div>{user.name}</div>;
}Server to Client: Children Pattern
Pass Server Components as children to Client Components.
// Client Component wrapper
'use client';
function Accordion({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && children}
</div>
);
}
// Server Component using client wrapper
export default async function FAQ() {
const faqs = await getFAQs();
return (
<Accordion>
{/* These remain Server Components */}
{faqs.map(faq => <FAQItem key={faq.id} faq={faq} />)}
</Accordion>
);
}Common Mistakes
Importing Server-Only Code in Client Components
// ❌ Error: Server-only code imported in client
'use client';
import { db } from '@/lib/db'; // Database client in client component!
// ✅ Fix: Use server-only package to prevent accidental imports
// lib/db.ts
import 'server-only';
import { PrismaClient } from '@prisma/client';
export const db = new PrismaClient();Serialization Errors
Client Components can only receive serializable props (no functions, Date objects, Maps, etc.).
// ❌ Error: Non-serializable prop
<ClientComponent
onClick={() => console.log('click')} // Functions can't be serialized
date={new Date()} // Date objects can't be serialized
/>
// ✅ Fix: Serialize data, handle events client-side
<ClientComponent
dateString={date.toISOString()} // Serializable string
/>Unnecessary 'use client' on Components Without Interactivity
// ❌ Unnecessary: No hooks or event handlers
'use client';
function Footer() {
return <footer>© 2024 My App</footer>;
}
// ✅ Fix: Remove 'use client' — this is pure rendering
function Footer() {
return <footer>© 2024 My App</footer>;
}Third-Party Library Integration
Context Providers
Wrap context providers in a Client Component at the layout level.
// app/providers.tsx
'use client';
import { ThemeProvider } from 'next-themes';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
</QueryClientProvider>
);
}
// app/layout.tsx (Server Component)
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Review Checklist for Component Boundaries
- [ ]
'use client'is only on components that need interactivity - [ ]
'use client'is as deep in the component tree as possible - [ ] Server Components don't import client-only modules
- [ ] Client Components don't import
server-onlymodules - [ ] Props passed from Server to Client Components are serializable
- [ ] Context providers are wrapped in a dedicated Client Component
- [ ] Data fetching happens in Server Components, not Client Components
- [ ] Sensitive data (API keys, secrets) stays in Server Components only
- [ ] Third-party libraries that use hooks are wrapped in Client Components
Related skills
Forks & variants (1)
Nextjs Code Review has 1 known copy in the catalog totaling 3 installs. They canonicalize to this original listing.
- giuseppe-trisciuoglio - 3 installs
How it compares
Use nextjs-code-review for App Router PR audits; use nextjs-deployment when the task is shipping builds to Vercel or AWS.
FAQ
What does nextjs-code-review do?
Provides comprehensive code review capability for Next.js applications, validates Server Components, Client Components, Server Actions, caching strategies, metadata, API routes, middleware, and perfor
When should I use nextjs-code-review?
During build backend work for backend & apis.
Is nextjs-code-review safe to install?
Review the Security Audits panel on this listing before production use.