Expert Next.js Best Practices Suite
Professional-grade skill designed for next.js routing, caching, and rendering strategies. Built for Claude Code with best practices and real-world patterns.
Next.js Best Practices Suite
Production-grade Next.js development patterns covering App Router architecture, server components, data fetching strategies, caching, middleware, deployment optimization, and performance tuning.
When to Use This Skill
Choose Next.js Best Practices when:
- Building or optimizing Next.js applications with App Router
- Implementing server-side rendering (SSR) and static generation (SSG) strategies
- Configuring caching, ISR, and revalidation for optimal performance
- Setting up authentication, middleware, and API routes
- Deploying Next.js applications to production environments
Consider alternatives when:
- Building a pure SPA — use Vite + React
- Need a static site only — use Astro or 11ty
- Building a backend API only — use Express, Fastify, or Hono
Quick Start
# Activate Next.js best practices claude skill activate expert-next-js-best-practices-suite # Review application architecture claude "Review my Next.js App Router architecture for best practices" # Optimize data fetching claude "Optimize the data fetching strategy in the dashboard pages"
Example: App Router Page Structure
// app/dashboard/page.tsx — Server Component (default) import { Suspense } from 'react'; import { DashboardStats } from './components/DashboardStats'; import { RecentActivity } from './components/RecentActivity'; export const metadata = { title: 'Dashboard | MyApp', description: 'View your activity and stats', }; export default async function DashboardPage() { // This fetch is cached and deduplicated automatically const user = await getUser(); return ( <main className="container mx-auto p-6"> <h1 className="text-2xl font-bold mb-6">Welcome, {user.name}</h1> <Suspense fallback={<StatsSkeletons />}> <DashboardStats userId={user.id} /> </Suspense> <Suspense fallback={<ActivitySkeleton />}> <RecentActivity userId={user.id} /> </Suspense> </main> ); } // app/dashboard/components/DashboardStats.tsx async function DashboardStats({ userId }: { userId: string }) { const stats = await fetchStats(userId); return ( <div className="grid grid-cols-3 gap-4"> {stats.map(stat => ( <StatCard key={stat.label} {...stat} /> ))} </div> ); }
Core Concepts
Rendering Strategies
| Strategy | When to Use | Caching |
|---|---|---|
| Static (SSG) | Content doesn't change per-request | Build time, CDN cached |
| ISR | Content changes periodically | Revalidate on interval |
| SSR | Content changes per-request | No cache (or short TTL) |
| Client-side | User-specific interactive content | Browser cache |
| Streaming | Large pages with independent sections | Incremental delivery |
Data Fetching Patterns
| Pattern | Implementation | Best For |
|---|---|---|
| Server Component fetch | async function Page() { const data = await fetch() } | Initial page data |
| Route Handler | app/api/route.ts with GET/POST handlers | API endpoints |
| Server Action | 'use server' functions for mutations | Form submissions, mutations |
| Client fetch | useQuery with TanStack Query | Dynamic client-side data |
| Parallel fetching | Promise.all([fetch1(), fetch2()]) | Independent data sources |
| Preloading | preload() pattern | Critical data warm-up |
// Caching and revalidation patterns // Static: cached indefinitely (default) const data = await fetch('https://api.example.com/posts'); // Revalidate every 60 seconds (ISR) const data = await fetch('https://api.example.com/posts', { next: { revalidate: 60 }, }); // No cache: always fresh (SSR) const data = await fetch('https://api.example.com/user', { cache: 'no-store', }); // Tag-based revalidation const data = await fetch('https://api.example.com/posts', { next: { tags: ['posts'] }, }); // Revalidate by tag in a Server Action 'use server'; import { revalidateTag } from 'next/cache'; export async function createPost() { await db.posts.create({ ... }); revalidateTag('posts'); }
Configuration
| Parameter | Description | Default |
|---|---|---|
app_router | Use App Router (recommended) | true |
typescript | TypeScript enabled | true |
styling | Styling: tailwind, css-modules, styled | tailwind |
data_fetching | Primary strategy: server-components, swr, react-query | server-components |
auth | Auth solution: next-auth, clerk, custom | next-auth |
deployment | Target: vercel, docker, standalone | vercel |
image_optimization | Use next/image component | true |
Best Practices
-
Default to Server Components, add 'use client' only when needed — Server Components reduce JavaScript bundle size and enable direct database/API access. Only add
'use client'for interactivity (event handlers, hooks, browser APIs). Never make a parent client component when only a child needs interactivity. -
Use Suspense boundaries for independent data sections — Wrap each data-dependent section in its own
<Suspense>boundary. This enables streaming SSR where fast sections render immediately while slow sections show loading states, improving perceived performance. -
Implement proper error boundaries at every route segment — Create
error.tsxfiles at each route level to catch and handle errors gracefully. Include recovery actions so users don't need to refresh the entire page when a single section fails. -
Use Server Actions for all mutations, not API routes — Server Actions are type-safe, progressively enhanced, and integrate with React's form system. Reserve API routes for external API consumers, webhooks, and non-React clients.
-
Optimize images with next/image and configure remote patterns — Always use the
<Image>component for automatic lazy loading, sizing, and format conversion. ConfigureremotePatternsinnext.config.jsfor external image domains rather than usingunoptimized.
Common Issues
Hydration mismatch errors between server and client rendering. This happens when server-rendered HTML differs from client-rendered output. Common causes: using Date.now(), Math.random(), or browser-only APIs during server rendering. Use useEffect for client-only code and suppressHydrationWarning only for intentional differences like timestamps.
Caching behavior is confusing — stale data appears after mutations. Next.js aggressively caches fetches in Server Components. After mutations, call revalidatePath() or revalidateTag() in your Server Action to invalidate relevant caches. Use the cache: 'no-store' option for data that must always be fresh.
Bundle size grows large despite using Server Components. Check that you haven't accidentally added 'use client' to a parent layout that makes all children client components. Use @next/bundle-analyzer to identify large client-side dependencies and consider moving them to Server Components or dynamically importing them.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.