
Nextjs Code Review
Review Next.js App Router routes for layouts, loading, error, and not-found patterns before merge or release.
Install
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-code-reviewWhat is this skill?
- Route segment patterns for shared layouts wrapping persistent dashboard chrome
- loading.tsx guidance for Suspense-aligned skeleton UI
- error.tsx client boundaries with reset recovery and digest-aware alerts
- not-found.tsx conventions for segment-level 404 UX
- App Router file conventions as review criteria for Next.js code review passes
Adoption & trust: 992 installs on skills.sh; 271 GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Improve Codebase Architecturemattpocock/skills
Zoom Outmattpocock/skills
Caveman Reviewjuliusbrussee/caveman
Requesting Code Reviewobra/superpowers
Receiving Code Reviewobra/superpowers
Request Refactor Planmattpocock/skills
Journey fit
Primary fit
Canonical shelf is Ship review because the skill encodes merge-ready quality gates on App Router structure rather than greenfield scaffolding alone. Review subphase matches checklist-driven inspection of segment files, Suspense boundaries, and client error recovery hooks.
Common Questions / FAQ
Is Nextjs Code Review safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Nextjs Code Review
# Next.js App Router Patterns ## Route Segment Patterns ### Layouts Layouts wrap child segments and persist across navigations. Use for shared UI (headers, sidebars, navigation). ```tsx // 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. ```tsx // 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. ```tsx // 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. ```tsx // 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. ```tsx // 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. ```tsx 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. ```tsx 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 ```tsx // 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 ```tsx 'use client'; import { useOptimistic } from 'react'; export function TodoList({ todos }: { todos: Todo[] }) { const [optimisticTodos, addOptimistic] = useOptimistic( todos, (state, newTodo: Todo)