Why Your Next.js App Is Slow (And How to Fix It)
Next.js gives you a fast app by default. Server Components, automatic code splitting, image optimization, font loading — it's all built in. So if your Next.js app is slow, you're actively fighting the framework.
Here are the most common performance killers I've seen in production Next.js apps, with real fixes for each.
1. You're Shipping Too Much JavaScript
The Problem
Every 'use client' component and its entire dependency tree ships to the browser. One careless import can add hundreds of kilobytes to your bundle.
The Fix
Analyze your bundle first. You can't fix what you can't measure:
Use specific imports:
Dynamic imports for heavy components:
Keep components server-side by default. Only add 'use client' when you need interactivity (useState, useEffect, onClick, etc.). Server Components ship zero JavaScript.
2. Your Images Are Unoptimized
The Problem
Raw images are the single biggest payload on most websites. A single unoptimized hero image can be 2-5MB.
The Fix
Key rules:
- Use
priorityonly for the Largest Contentful Paint (LCP) image - Always provide
sizesso the browser picks the right image size - Use
placeholder="blur"for perceived performance - Let Next.js handle format conversion (WebP/AVIF)
3. Your Fonts Cause Layout Shift
The Problem
Custom fonts load asynchronously. If not handled properly, text renders in a fallback font, then "jumps" when the custom font loads. This is called FOUT (Flash of Unstyled Text) and kills your CLS (Cumulative Layout Shift) score.
The Fix
next/font automatically:
- Self-hosts the font (no external requests to Google Fonts)
- Generates size-adjusted fallback fonts (minimizing layout shift)
- Preloads the font file
Never load fonts with <link> tags. Always use next/font.
4. You're Not Streaming
The Problem
Without streaming, the entire page waits for the slowest data fetch before anything renders:
The Fix
Use Suspense to stream parts of the page independently:
Now the user sees the profile in 200ms. Analytics streams in 2 seconds later with a skeleton placeholder in between.
5. You're Over-Fetching Data
The Problem
Fetching data on every request when it doesn't change often:
The Fix
Use appropriate caching strategies:
6. Your Third-Party Scripts Block Rendering
The Problem
Analytics, chat widgets, and tracking pixels can block the main thread:
The Fix
7. You're Not Using Parallel Data Fetching
The Problem
Sequential fetches when the data is independent:
The Fix
This alone can cut your page load time in half.
The Performance Checklist
Before shipping, verify:
- Bundle analyzer shows no unexpected large dependencies
- No
'use client'on components that don't need interactivity - Images use
next/imagewith proper sizes and priority - Fonts use
next/font(no external<link>tags) - Slow data fetches wrapped in
<Suspense> - Independent fetches use
Promise.all - Third-party scripts use
next/scriptwith proper strategy - Static pages use ISR or SSG (not force-dynamic)
- No barrel file re-exports pulling in unused code
Wrapping Up
Next.js performance isn't about clever tricks. It's about not fighting the framework:
- Ship less JavaScript — Server Components by default, dynamic imports for heavy stuff
- Optimize images — Use next/image, always
- Fix fonts — Use next/font, always
- Stream content — Suspense boundaries around slow data
- Cache aggressively — ISR over force-dynamic when possible
- Parallelize — Promise.all for independent fetches
- Defer scripts — next/script with afterInteractive or lazyOnload
Measure first. Fix the biggest bottleneck. Repeat.