Core Web Vitals are one of the most important ranking factors for Google in 2025.
If your Next.js website is slow or unstable, you’ll see warnings like:
- ❗ Poor LCP (Largest Contentful Paint)
- ❗ CLS (Cumulative Layout Shift)
- ❗ TTI (Time to Interactive)
The good news?
Next.js gives you everything needed to fix these — if you structure your app correctly.
Let’s break down what causes bad Web Vitals and how to fix them step-by-step in a simple, human way.
⭐ What Are Core Web Vitals?
1. LCP – Largest Contentful Paint
How fast your biggest element loads (hero image, heading, banner).
2. CLS – Cumulative Layout Shift
Does your layout jump when loading? (fonts, ads, images, dynamic content)
3. TTI – Time to Interactive
How fast the page becomes usable (JS hydration, heavy scripts, client components)
🚨 Why Next.js Sites Fail Core Web Vitals
Common issues:
- Using Client Components everywhere
- Large or unoptimized hero images
- Not using next/image
- Layout shifts from missing width/height
- Loading fonts incorrectly
- Heavy JS from UI libraries
- Not using streaming or Server Components
- Hydration issues from dynamic imports
Let’s fix each one.
✅ 1. Fix LCP (Largest Contentful Paint)
Problem: Hero image loads too late or too large.
Solution 1: Use next/image always
Why this works:
- priority forces the image to load early
- Correct width/height prevents CLS
- Next.js automatically optimizes formats (WebP, AVIF)
Solution 2: Move heavy React code to Server Components
Server Components reduce JS shipped to the browser → faster LCP.
Solution 3: Remove large blocking scripts
- Remove unused analytics
- Move scripts to afterInteractive
Solution 4: Self-host fonts properly
Google Fonts = LCP killer.
Use Next.js built-in font optimizer:
This eliminates render-blocking font loads.
✅ 2. Fix CLS (Cumulative Layout Shift)
Problem: Layout moves around during load.
Common causes:
- Images without width/height
- Loading ads/widgets dynamically
- Late-loading fonts
- Sticky header shifting on scroll
- Carousels without fixed size
Let’s fix.
Solution 1: Give all images fixed size
Never use images like this ❌
Always use ❌ width + height OR fill:
Solution 2: Reserve space for dynamic sections
If an element loads late (chat widget, ads), reserve space:
Solution 3: Fix layout shift from fonts
Use:
“swap” avoids invisible text → big CLS fix.
Solution 4: Don’t animate layout during hydration
Example bad:
Better:
or manage animations after mount.
Solution 5: Use stable header height
Your header should not change size when scrolled.
Use:
✅ 3. Fix TTI (Time to Interactive)
Problem: Page loads but buttons don’t work instantly.
This happens when:
- Too many Client Components
- Heavy JavaScript
- Large UI libraries being loaded
- Hydration takes long
- Dynamic imports used incorrectly
Solution 1: Convert more components to Server Components
Wrong ❌ (everything client):
Correct ✅:
Only interactive components should use "use client":
Your TTI will instantly improve because less JS = faster interactivity.
Solution 2: Use dynamic imports
Instead of loading everything:
Load only when needed:
Reduces TTI dramatically.
Solution 3: Reduce JavaScript shipped
Avoid:
- MUI (heavy)
- Full lodash
- Big date libraries
Use smaller alternatives:
- date-fns
- lodash-es tree-shaking
- Radix UI instead of MUI
Solution 4: Use React Server Components for data
Old way (slow TTI):
New way (fast):
Zero JS required on the client.
🎯 Bonus: Tools to Debug Web Vitals
1. Chrome Lighthouse / DevTools
Performance → Web Vitals
2. Pagespeed Insights
Excellent lab + field data

