E

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.

SkillCommunityfrontendv1.0.0MIT
0 views0 copies

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

StrategyWhen to UseCaching
Static (SSG)Content doesn't change per-requestBuild time, CDN cached
ISRContent changes periodicallyRevalidate on interval
SSRContent changes per-requestNo cache (or short TTL)
Client-sideUser-specific interactive contentBrowser cache
StreamingLarge pages with independent sectionsIncremental delivery

Data Fetching Patterns

PatternImplementationBest For
Server Component fetchasync function Page() { const data = await fetch() }Initial page data
Route Handlerapp/api/route.ts with GET/POST handlersAPI endpoints
Server Action'use server' functions for mutationsForm submissions, mutations
Client fetchuseQuery with TanStack QueryDynamic client-side data
Parallel fetchingPromise.all([fetch1(), fetch2()])Independent data sources
Preloadingpreload() patternCritical 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

ParameterDescriptionDefault
app_routerUse App Router (recommended)true
typescriptTypeScript enabledtrue
stylingStyling: tailwind, css-modules, styledtailwind
data_fetchingPrimary strategy: server-components, swr, react-queryserver-components
authAuth solution: next-auth, clerk, customnext-auth
deploymentTarget: vercel, docker, standalonevercel
image_optimizationUse next/image componenttrue

Best Practices

  1. 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.

  2. 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.

  3. Implement proper error boundaries at every route segment — Create error.tsx files 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.

  4. 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.

  5. Optimize images with next/image and configure remote patterns — Always use the <Image> component for automatic lazy loading, sizing, and format conversion. Configure remotePatterns in next.config.js for external image domains rather than using unoptimized.

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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates