← All posts
next.jscore web vitalsperformanceseolcpfidcls

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

O

OverMCP Team

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

TL;DR: To improve Core Web Vitals in Next.js, focus on optimizing images (use next/image), reduce JavaScript execution time (use dynamic imports and lazy loading), and prevent layout shifts (reserve space for images and ads). This guide walks through each metric with concrete code examples.

Core Web Vitals are a set of metrics that Google uses to measure user experience. They include Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS). For Next.js apps, these metrics are crucial for SEO and user retention. Below, I'll show you exactly how to improve each one.

1. Optimize Largest Contentful Paint (LCP)

The LCP element is typically a hero image, video poster, or large text block. Here's how to optimize it in Next.js.

Use the `next/image` Component

Next.js provides the Image component that automatically optimizes images. It serves responsive images in modern formats like WebP, lazy-loads offscreen images, and resizes them on the fly.

import Image from 'next/image';

export default function Hero() {
  return (
    <div style={{ position: 'relative', width: '100%', height: '500px' }}>
      <Image
        src="/hero.jpg"
        alt="Hero image"
        fill
        priority
        sizes="(max-width: 768px) 100vw, 50vw"
      />
    </div>
  );
}

Key points:

  • Use priority for above-the-fold images to skip lazy loading.
  • Always set sizes to help the browser pick the right image size.
  • Use fill with a parent container that has position: relative.
  • Preload Critical Resources

    Use the next/head component to preload fonts, images, or scripts that are critical for LCP.

    import Head from 'next/head';
    
    export default function Layout({ children }) {
      return (
        <>
          <Head>
            <link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
          </Head>
          {children}
        </>
      );
    }

    Minimize Render-Blocking Resources

    Reduce the impact of CSS and JavaScript that blocks rendering. Next.js automatically inlines critical CSS for the initial page load (since version 12), but you can further optimize:

  • Use next/script with strategy="afterInteractive" for non-critical scripts.
  • Avoid importing large CSS libraries globally; use CSS modules or Tailwind's purge.
  • 2. Improve First Input Delay (FID) / Interaction to Next Paint (INP)

    FID measures the time from when a user first interacts with your app to when the browser can respond. INP is a more comprehensive metric coming in 2024. The goal is to keep the main thread free.

    Reduce JavaScript Execution Time

  • Dynamic imports: Split code so that chunks are loaded on demand.
  • import dynamic from 'next/dynamic';
    
    const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
      loading: () => <p>Loading...</p>,
    });
  • Lazy load below-the-fold components using Next.js dynamic with ssr: false for client-only components.
  • Use `next/dynamic` for third-party widgets like chat bots or maps.
  • Optimize Third-Party Scripts

    Use next/script with the appropriate strategy:

    import Script from 'next/script';
    
    export default function Page() {
      return (
        <>
          <Script
            src="https://analytics.example.com/script.js"
            strategy="lazyOnload"
          />
          <Script
            src="https://widget.example.com/widget.js"
            strategy="afterInteractive"
          />
        </>
      );
    }
  • beforeInteractive: For critical scripts (e.g., polyfills).
  • afterInteractive: For scripts that can wait until the page is interactive.
  • lazyOnload: For non-essential scripts (e.g., analytics).
  • Avoid Long Tasks

    Break up long tasks using setTimeout or requestIdleCallback. Use tools like the Performance panel in Chrome DevTools to identify long tasks.

    3. Minimize Cumulative Layout Shift (CLS)

    CLS measures visual stability. It occurs when elements shift after the user has started interacting.

    Reserve Space for Images

    When using next/image, always set width and height or use fill with a container that has a defined aspect ratio.

    <div style={{ position: 'relative', width: '100%', paddingTop: '56.25%' }}>
      <Image src="/video-thumbnail.jpg" alt="Video" fill />
    </div>

    This ensures the browser reserves the correct space before the image loads.

    Reserve Space for Ads and Embeds

    If you use ads or embeds, wrap them in a container with a fixed size or aspect ratio.

    <div style={{ width: '300px', height: '250px', background: '#f0f0f0' }}>
      {/* Ad code */}
    </div>

    Avoid Inserting Content Above Existing Content

    Dynamic inserts (e.g., banners, cookie notices) should not push down existing content. Use position: fixed or position: sticky to overlay them without affecting layout.

    Use `next/font` for Font Optimization

    Font loading can cause layout shifts. Next.js 13+ includes next/font that automatically optimizes fonts and eliminates layout shift.

    import { Inter } from 'next/font/google';
    
    const inter = Inter({ subsets: ['latin'] });
    
    export default function Layout({ children }) {
      return <div className={inter.className}>{children}</div>;
    }

    4. General Performance Tips

    Enable Incremental Static Regeneration (ISR)

    For content that changes infrequently, use ISR to serve static pages with periodic revalidation.

    export async function getStaticProps() {
      const data = await fetch('https://api.example.com/posts');
      const posts = await data.json();
    
      return {
        props: { posts },
        revalidate: 60, // seconds
      };
    }

    Use CDN and Caching

    Next.js automatically serves static assets from a CDN when deployed on Vercel or Netlify. Ensure caching headers are set for images and static files.

    Monitor with Real User Monitoring (RUM)

    Use tools like web-vitals library in Next.js to track Core Web Vitals in production.

    import { useEffect } from 'react';
    import { getCLS, getFID, getLCP } from 'web-vitals';
    
    export default function App({ Component, pageProps }) {
      useEffect(() => {
        getCLS(console.log);
        getFID(console.log);
        getLCP(console.log);
      }, []);
    
      return <Component {...pageProps} />;
    }

    5. Tools to Measure Core Web Vitals

  • Lighthouse: Built into Chrome DevTools. Run an audit to get specific recommendations.
  • PageSpeed Insights: Google's tool that provides lab and field data.
  • Web Vitals Extension: Chrome extension that shows real-time metrics.
  • Search Console: Shows Core Web Vitals reports for your site.
  • 6. Common Pitfalls in Next.js

  • Not using `next/image`: Custom image optimization often leads to large LCP.
  • Global CSS imports: Importing large CSS files in _app.js blocks rendering. Use CSS modules or Tailwind's purge.
  • Server-side rendering everything: For dynamic pages, consider static generation or ISR.
  • Ignoring third-party scripts: Many scripts are render-blocking. Use next/script with appropriate strategies.
  • FAQ

    How do I check Core Web Vitals in Next.js?

    You can use the web-vitals library in your app to log metrics, or use Google's PageSpeed Insights and Chrome DevTools Lighthouse tab to analyze your site.

    What is the most important Core Web Vital for SEO?

    Google considers all three metrics (LCP, FID, CLS) equally, but LCP is often the hardest to optimize. A poor LCP can significantly affect user experience and rankings.

    Can I ignore Core Web Vitals if my site loads fast?

    No. Fast load times don't guarantee good Core Web Vitals. For example, a fast-loading page can still have CLS issues from late-loading images. Always measure and optimize for each metric.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free