Next.js Performance Optimization
Before Starting
- Analyze current performance with Lighthouse
- Identify bottlenecks - check Core Web Vitals in Chrome DevTools or PageSpeed Insights
- Determine optimization priority:
- LCP issues → Focus on images, fonts
- INP issues → Reduce JS, use Server Components
- CLS issues → Add dimensions, use next/font
Core Principles
- Prefer Server Components - Only use 'use client' when necessary (browser APIs, interactivity)
- Load components as low as possible - Keep Client Components at leaf nodes
- Use Suspense boundaries - Enable streaming and progressive loading
- Cache appropriately - Use tags for granular revalidation
- Measure before/after - Always verify improvements with real metrics
References
Each file is loaded on demand — read one only when the task needs that depth (progressive disclosure).
references/core-web-vitals.md— LCP/INP/CLS targets, diagnostics, and per-metric optimizations · read when measuring or fixing Core Web Vitals.references/image-optimization.md— next/image sizing, priority, blur placeholder, remotePatterns config · read when fixing LCP or image-related CLS.references/font-optimization.md— next/font setup, subsets, display swap, CSS variable integration · read when eliminating font-related CLS or FOUT.references/caching-strategies.md— unstable_cache, revalidateTag/Path, ISR, cache tags, TTL guidance · read when configuring data or route caching.references/server-components.md— Client-to-Server conversion patterns, RSC constraints, Suspense boundaries · read when moving components server-side or wiring streaming.references/streaming-suspense.md— Suspense boundaries, progressive loading, fallback skeletons · read when implementing streaming or reducing TTFB.references/bundle-optimization.md— dynamic(), code splitting, tree shaking, modularizeImports, bundle-analyzer · read when reducing initial JS bundle size.references/metadata-seo.md— static/generateMetadata, Open Graph, canonical, sitemap · read when configuring metadata or SEO.references/api-routes.md— Route Handler patterns, Edge runtime, input validation, error handling · read when optimizing API routes.references/nextjs-16-patterns.md— async params Promise, use() hook, useOptimistic, React 19 patterns · read when adopting Next.js 15+/React 19 APIs.
Common Conversions
| From | To | Benefit |
|---|---|---|
useEffect + fetch |
Direct async in Server Component | -70% JS, faster TTFB |
useState for data |
Server Component with direct DB access | Simpler code, no hydration |
| Client-side fetch | unstable_cache or ISR |
Faster repeated loads |
img tag |
next/image |
Optimized formats, lazy loading |
| CSS font import | next/font |
Zero CLS, automatic optimization |
| Static import of heavy component | dynamic() |
Reduced initial bundle |
Constraints and Warnings
Server Components Limitations
- Cannot use browser APIs (window, localStorage, document)
- Cannot use React hooks (useState, useEffect, useContext)
- Cannot use event handlers (onClick, onSubmit)
- Cannot use dynamic imports with ssr: false
Image Optimization Constraints
priorityshould only be used for above-the-fold images- External images require configuration in next.config.js
widthandheightare required unless usingfill- Animated GIFs are not optimized by default
Caching Considerations
- Cache tags must be manually invalidated
- Data cache is per-request in development
- Edge runtime has different caching behavior
- Be careful caching user-specific data
Bundle Size Warnings
- Dynamic imports can impact SEO if critical content
- Tree shaking requires proper ES module usage
- Some libraries cannot be tree shaken (avoid barrel exports)
- Client Components increase bundle size - use sparingly
Performance Checklist
- All images use
next/imagewith proper dimensions - LCP images have
priorityattribute - Fonts use
next/fontwith subsets - Server Components used where possible
- Client Components at leaf level only
- Suspense boundaries for data fetching
- Caching configured for expensive operations
- Bundle analyzed for duplicates
- Heavy components lazy loaded
- Lighthouse score verified before/after
Common Mistakes
// ❌ DON'T: Fetch in useEffect
'use client'
useEffect(() => { fetch('/api/data').then(...) }, [])
// ✅ DO: Fetch directly in Server Component
const data = await fetch('/api/data')
// ❌ DON'T: Forget dimensions on images
<Image src="/photo.jpg" />
// ✅ DO: Always provide dimensions
<Image src="/photo.jpg" width={800} height={600} />
// ❌ DON'T: Use priority on all images
<Image src="/photo1.jpg" priority />
<Image src="/photo2.jpg" priority />
// ✅ DO: Priority only for LCP
<Image src="/hero.jpg" priority />
<Image src="/photo.jpg" loading="lazy" />
// ❌ DON'T: Cache everything with same TTL
{ revalidate: 3600 }
// ✅ DO: Match TTL to data change frequency
{ revalidate: 86400 } // Categories rarely change
{ revalidate: 60 } // Comments change often