How to Fix Core Web Vitals in Next.js: A Complete Guide

How to Fix Core Web Vitals in Next.js: A Complete Guide
Core Web Vitals are Google's metrics for measuring user experience - and they directly impact your search rankings. Here's how to optimize all three metrics in Next.js.

What Are Core Web Vitals?
Google measures three key metrics:
- ▸
LCP (Largest Contentful Paint): How fast your main content loads
- ▸Good: <2.5s | Needs improvement: 2.5-4s | Poor: >4s
- ▸
FID (First Input Delay) / INP (Interaction to Next Paint): How responsive your site is
- ▸Good: <100ms | Needs improvement: 100-300ms | Poor: >300ms
- ▸
CLS (Cumulative Layout Shift): How stable your layout is during load
- ▸Good: <0.1 | Needs improvement: 0.1-0.25 | Poor: >0.25

Optimizing LCP (Largest Contentful Paint)
1. Use Next.js Image Component
The `next/image` component automatically optimizes images:
```jsx import Image from 'next/image';
export default function Hero() { return ( <Image src="/hero-image.jpg" alt="Hero" width={1920} height={1080} priority // Loads immediately, not lazy quality={75} // Balance quality and file size /> ); } ```
2. Preload Critical Resources
In your `_app.tsx` or `layout.tsx`:
```jsx import Head from 'next/head';
export default function Layout() { return ( <> <Head> <link rel="preload" href="/fonts/your-font.woff2" as="font" type="font/woff2" crossOrigin="anonymous" /> </Head> {/* Your content */} </> ); } ```
3. Use Server Components (App Router)
Server Components render on the server, reducing JavaScript:
```tsx // app/page.tsx (Server Component by default) export default async function HomePage() { const data = await fetch('https://api.example.com/data');
return <div>{/* Rendered on server */}</div>; } ```
4. Implement Streaming SSR
Stream HTML as it's generated:
```tsx import { Suspense } from 'react';
export default function Page() { return ( <> <Header /> {/* Renders immediately /} <Suspense fallback={<Skeleton />}> <SlowComponent /> {/ Streams in when ready */} </Suspense> </> ); } ```

Optimizing FID/INP (Interactivity)
1. Code Splitting and Dynamic Imports
Don't load components until needed:
```jsx import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), { ssr: false, // Skip server-side rendering loading: () => <p>Loading chart...</p>, });
export default function Dashboard() { return <HeavyChart />; } ```
2. Reduce JavaScript Bundle Size
Check your bundle: ```bash npm run build
Look for large chunks
```
Optimize imports: ```jsx // ❌ Bad: Imports entire library import _ from 'lodash';
// ✅ Good: Import only what you need import debounce from 'lodash/debounce'; ```
3. Use `useTransition` for Non-Urgent Updates
```jsx import { useTransition, useState } from 'react';
function SearchResults() { const [isPending, startTransition] = useTransition(); const [query, setQuery] = useState('');
function handleChange(e) { // Mark as non-urgent so user input stays responsive startTransition(() => { setQuery(e.target.value); }); }
return <input onChange={handleChange} />; } ```

Optimizing CLS (Cumulative Layout Shift)
1. Always Specify Image Dimensions
```jsx // ❌ Bad: Browser doesn't know size, causes shift <img src="/image.jpg" alt="Photo" />
// ✅ Good: Browser reserves space <Image src="/image.jpg" alt="Photo" width={800} height={600} /> ```
2. Reserve Space for Ads and Embeds
```jsx // Reserve space before ad loads
<div style={{ minHeight: '250px' }}> <AdComponent /> </div> \`\`\`3. Use CSS aspect-ratio
```css .video-container { aspect-ratio: 16 / 9; width: 100%; } ```
4. Avoid Inserting Content Above Existing Content
Don't dynamically inject banners/alerts at the top:
```jsx // ❌ Bad: Pushes content down function Banner() { const [show, setShow] = useState(false);
useEffect(() => { setTimeout(() => setShow(true), 2000); }, []);
return show ? <div>New banner!</div> : null; }
// ✅ Good: Position absolutely or fixed function Banner() { return ( <div style={{ position: 'fixed', top: 0, left: 0 }}> Banner content </div> ); } ```

Next.js Configuration for Performance
next.config.js
```javascript module.exports = { images: { formats: ['image/avif', 'image/webp'], deviceSizes: [640, 750, 828, 1080, 1200], imageSizes: [16, 32, 48, 64, 96], },
// Enable compression compress: true,
// Disable source maps in production productionBrowserSourceMaps: false,
// Optimize fonts optimizeFonts: true,
// Modern build target swcMinify: true, }; ```

Monitoring Core Web Vitals
1. Use web-vitals Package
```bash npm install web-vitals ```
```jsx // app/_app.tsx or app/layout.tsx import { getCLS, getFID, getLCP } from 'web-vitals';
function sendToAnalytics(metric) { // Send to your analytics console.log(metric); }
getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics); ```
2. Check Google Search Console
Your actual field data from real users:
- ▸Search Console → Core Web Vitals report
- ▸See which pages need improvement
3. Test with PageSpeed Insights
Run your URL through PageSpeed Insights or RoastWeb for detailed recommendations.

Quick Wins Checklist
- ▸[ ] Use `next/image` for all images with width/height
- ▸[ ] Enable `priority` on hero images
- ▸[ ] Lazy load images below the fold
- ▸[ ] Dynamic import heavy components
- ▸[ ] Preload critical fonts
- ▸[ ] Use Server Components where possible
- ▸[ ] Specify dimensions for all media
- ▸[ ] Optimize third-party scripts
- ▸[ ] Monitor with web-vitals package
Test your Next.js site's Core Web Vitals at RoastWeb.com

Key Takeaways
What You've Learned:
- ▸Core Web Vitals targets: LCP <2.5s, INP <200ms (replaced FID in 2024), CLS <0.1
- ▸Next.js Image component automatically optimizes images with WebP format and responsive sizing
- ▸INP measures ALL user interactions throughout page lifecycle, not just first click
- ▸CLS is caused by images without dimensions, dynamic content, and web fonts (FOUT)
- ▸Dynamic imports reduce JavaScript bundle size by loading components only when needed
- ▸Server Components in Next.js App Router send zero JavaScript to the client by default
Quick Wins:
- ▸Replace all `<img>` tags with Next.js `<Image>` component with width/height (1 hour)
- ▸Add `priority` prop to your hero image for faster LCP (2 min)
- ▸Dynamically import heavy components like charts with `next/dynamic` (30 min)
- ▸Add `font-display: swap` to your custom fonts to prevent FOUT (10 min)
- ▸Install web-vitals package and monitor metrics in production (15 min)
Frequently Asked Questions (FAQ)
What are Core Web Vitals in 2025?
Google's Core Web Vitals are: LCP (Largest Contentful Paint <2.5s), INP (Interaction to Next Paint <200ms - replaced FID in 2024), and CLS (Cumulative Layout Shift <0.1). These measure loading, interactivity, and visual stability.
How do I check Core Web Vitals for my Next.js site?
Use Google PageSpeed Insights (field + lab data), Google Search Console (Core Web Vitals report), or Next.js Analytics (Vercel). For local testing, use Lighthouse in Chrome DevTools.
Why is my Next.js site slow despite using SSR?
Common causes: unoptimized images, heavy client-side JavaScript, poor font loading, blocking third-party scripts, or no CDN. Use Next.js Image component, dynamic imports, and font optimization to fix.
What's the difference between INP and FID?
INP (Interaction to Next Paint) replaced FID (First Input Delay) in 2024. FID only measured first interaction. INP measures ALL interactions throughout the page lifecycle, providing a more accurate user experience metric.
Can I use third-party analytics without hurting Core Web Vitals?
Yes, with optimization. Load analytics asynchronously after page load, use Next.js Script component with strategy="afterInteractive" or strategy="lazyOnload", and minimize tracking pixels. Google Analytics 4 is lighter than Universal Analytics.
How do I improve LCP in Next.js?
Use next/image for automatic optimization, implement priority prop for hero images, enable image CDN, use ISR or SSG for static content, optimize server response time (TTFB), and remove render-blocking resources.