Next.js Performance Checklist: 20 Optimizations for 2026
A comprehensive Next.js performance checklist covering Core Web Vitals, server components, caching, and more. Get 90+ Lighthouse scores.
Core Web Vitals Basics
Google cares about three metrics. You should too.
LCP (Largest Contentful Paint): How fast does the main content appear? Target: under 2.5 seconds.
| What helps | What hurts |
|---|---|
| Server rendering | Large JavaScript bundles |
| Image optimization | Unoptimized images |
| Fast hosting | Slow API calls blocking render |
INP (Interaction to Next Paint): How responsive is the page to clicks? Target: under 200ms.
| What helps | What hurts |
|---|---|
| Minimal JavaScript | Heavy client-side processing |
| useTransition for slow updates | Blocking the main thread |
| Debouncing | Synchronous work on every keystroke |
CLS (Cumulative Layout Shift): Does stuff jump around while loading? Target: under 0.1.
| What helps | What hurts |
|---|---|
| Explicit image dimensions | Images without width/height |
| Placeholder space for dynamic content | Inserting content above existing content |
| Font-display: optional | Massive font-display: swap changes |
Server Components Done Right
Next.js 13+ made server components the default. Use them correctly.
Default to server components. They render on the server, send only HTML, and don't add to your JavaScript bundle.
Only use 'use client' when necessary. Anything interactive needs to be a client component: forms, buttons with handlers, components using hooks.
Push client boundaries down. Don't make a whole page a client component because one button needs onClick. Make just the button a client component.
| Pattern | Good or bad |
|---|---|
| Page as server component, interactive bits as client | Good |
| Entire page as client component | Usually bad |
| Server component fetching data, passing to client component | Good |
| Client component fetching data | Often unnecessary |
Images and Fonts
Images are usually the biggest performance problem.
Use next/image. Always. It handles lazy loading, responsive sizes, and modern formats automatically.
The key props:
| Prop | When to use |
|---|---|
| priority | Above-the-fold images, especially LCP |
| sizes | When image isn't full-width |
| placeholder="blur" | When you have a blur placeholder |
Use next/font. It handles font loading optimization automatically, including preventing CLS.
Import fonts once in your layout, use throughout the app. Don't load the same font multiple times.
Caching Strategies
Next.js caching is powerful but confusing. Here's the simple version:
Static pages (default): Built at deploy time, served from CDN. Maximum performance.
Revalidating pages: Static but refresh periodically. Use when content changes occasionally.
Dynamic pages: Rendered on every request. Use when content is personalized or real-time.
| Scenario | Strategy |
|---|---|
| Blog post | Static |
| Product page with inventory | Revalidate every 5 minutes |
| User dashboard | Dynamic |
| Homepage | Revalidate hourly |
For data fetching, Next.js caches fetch() calls by default. Override when needed:
| Need | Solution |
|---|---|
| Cache forever | fetch(url) (default) |
| Cache for 1 hour | fetch(url, { next: { revalidate: 3600 }}) |
| Never cache | fetch(url, { cache: 'no-store' }) |
Bundle Size
Smaller bundles = faster loads.
Check your bundle. Install @next/bundle-analyzer and run a build with ANALYZE=true. You'll see exactly what's making your bundle big.
Common culprits:
- Moment.js (use date-fns or dayjs instead)
- Lodash (import only what you need)
- Large UI libraries (do you need all of it?)
- Unused dependencies
Dynamic imports for heavy components. If you have a chart library, video player, or editor that's only used on some pages, dynamic import it.
Tree shaking depends on import style:
| Import style | Tree shaking |
|---|---|
| import { format } from 'date-fns' | Yes |
| import * as dateFns from 'date-fns' | No |
| import dateFns from 'date-fns' | No |
Quick checklist
Run through this before shipping:
- Images use next/image with explicit dimensions
- LCP image has priority prop
- Fonts use next/font
- Pages that can be static are static
- Bundle analyzer shows no obvious waste
- Third-party scripts are lazy loaded
- Core Web Vitals pass in Lighthouse
- No layout shift visible during load
If your Lighthouse score is under 90, something on this list is probably the cause.
📬 Get Engineering Insights
Practical articles on MVP development, legacy modernization, and building products that scale. Delivered to your inbox.
No spam. Unsubscribe anytime. We respect your privacy.
The Ordinary Company
Product-minded engineers helping startups build and scale. 50+ projects delivered.
Ready to Build Your Project?
Let's discuss how we can help bring your ideas to life. Free consultation, no strings attached.