ProgrUmar Logo
Module 4: Advanced SEO & Metadata

Core Web Vitals & Performance Auditing

Duration: 17 mins

Measuring What Google Measures

Install the web-vitals library and send scores to your analytics via the useReportWebVitals hook in a Client Component. Then use Lighthouse CI in your GitHub Actions pipeline to catch regressions before they ship.

Core Web Vitals & Performance Auditing

Building a website with great content and beautiful styling means nothing if your pages take too long to load or shift layouts unexpectedly during interaction. Since Google integrated user experience metrics directly into its search rank algorithms, site performance is a critical ranking factor. In this lesson, you will master the Core Web Vitals—including Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and Interaction to Next Paint (INP)—and learn how to audit, monitor, and optimize your Next.js application to achieve a perfect 100 score in Lighthouse audits.


1. Understanding Google's Core Web Vitals Metrics

Google evaluates user experience through three primary Core Web Vitals. Each represents a distinct facet of real-world speed and responsiveness:

Metric Measures Ideal Score Primary Next.js Focus
LCP (Largest Contentful Paint) Loading Speed < 2.5 seconds Image optimization, Server response times (TTFB), CDN caching.
CLS (Cumulative Layout Shift) Visual Stability < 0.1 Explicit dimension attributes, layout shells, font-display strategies.
INP (Interaction to Next Paint) Interactivity/Responsiveness < 200 milliseconds Minimizing main-thread JavaScript blockage, optimizing event handlers.

2. Largest Contentful Paint (LCP) Optimization

LCP measures when the largest element above the fold (typically a hero image or main heading block) becomes visible. To optimize LCP, you must eliminate server-side latency and optimize dynamic assets.

LCP Best Practices:

  • Use Server Components to render initial HTML layouts directly on the edge.
  • Apply the priority attribute on images that serve as the main LCP element to force preloading.
  • Utilize connection prefetching (e.g. dns-prefetch or preconnect) on external media sources.
// components/HeroBanner.tsx
import Image from 'next/image';

export default function HeroBanner() {
  return (
    <header className="relative w-full h-[400px]">
      <Image
        src="https://images.unsplash.com/photo-1555066931-4365d14bab8c"
        alt="Developer Workspace"
        fill
        sizes="100vw"
        priority // Instructs Next.js to preload this asset instantly for LCP
        className="object-cover"
      />
      <h1 className="relative z-10 text-white font-bold text-4xl p-8">
        Master Fullstack Engineering
      </h1>
    </header>
  );
}

3. Eliminating Cumulative Layout Shift (CLS)

Layout shifts occur when visual elements change position on the screen without user action. This typically happens when media assets lack predefined aspect ratios or custom fonts load late and override fallback typography styles.

CLS Prevention Strategies:

  • Set explicit width and height dimensions on all image or video nodes.
  • Ensure advertisement slots or dynamic notification banners have reserved placeholder layouts.
  • Utilize modern font hosting styles that eliminate font swapping flash effects.
// Bad: CLS issues will occur because height is auto-resolved after fetch
export function BadImage() {
  return <img src="/banners/promo.png" alt="Promo Banner" />;
}

// Good: Browser reserves the correct space immediately before download completes
export function GoodImage() {
  return (
    <div className="relative w-full h-0 pb-[56.25%] overflow-hidden bg-neutral-100">
      <img 
        src="/banners/promo.png" 
        alt="Promo Banner" 
        className="absolute top-0 left-0 w-full h-full object-cover" 
      />
    </div>
  );
}

4. Interaction to Next Paint (INP) Optimization

INP replaced First Input Delay (FID) as a core metric. It tracks the latency of all user interactions (clicks, taps, typing) throughout the lifecycle of a page, evaluating how long the browser takes to repaint the screen after an interaction occurs.

To improve INP, avoid running heavy JavaScript calculations on the main thread during user interactions. Use Web Workers or split long tasks using standard callbacks:

// Optimizing long tasks for lower INP scores
export function SearchComponent() {
  const handleSearch = async (query: string) => {
    // 1. Update UI state immediately (high priority)
    showLoadingSpinner();

    // 2. Yield control back to the browser to paint the spinner
    await new Promise((resolve) => setTimeout(resolve, 0));

    // 3. Perform heavy computation or network fetch
    const results = performHeavyQuery(query);
    renderResults(results);
  };

  return <input onChange={(e) => handleSearch(e.target.value)} />;
}

5. Programmatic Measurement with useReportWebVitals

To monitor real-world user metrics (Real User Monitoring, or RUM), Next.js provides a built-in Hook called useReportWebVitals. This hook catches client-side metrics and fires events whenever a Core Web Vital value is recorded.

Create a dedicated Client Component to initialize this tracker in your root layout:

// components/WebVitalsReporter.tsx
'use client';

import { useReportWebVitals } from 'next/navigation';

export default function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    const { id, name, label, value } = metric;
    
    // Log details to console or send to an analytics collection server
    console.log({
      metricId: id,
      metricName: name,
      metricLabel: label,
      metricValue: value,
    });
  });

  return null; // This component has no visual UI
}

6. Reporting Core Web Vitals to Custom Endpoints

Logging to the console is useful for local debugging, but production environments require centralized monitoring. You can forward metrics gathered from useReportWebVitals to your own analytics API endpoint or platforms like Google Analytics:

// components/AnalyticsReporter.tsx
'use client';

import { useReportWebVitals } from 'next/navigation';

export default function AnalyticsReporter() {
  useReportWebVitals((metric) => {
    const body = JSON.stringify({
      id: metric.id,
      name: metric.name,
      value: String(metric.value),
      path: window.location.pathname,
    });

    // Use sendBeacon if supported for non-blocking background transport
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/analytics/vitals', body);
    } else {
      fetch('/api/analytics/vitals', {
        method: 'POST',
        body,
        keepalive: true,
        headers: { 'Content-Type': 'application/json' },
      });
    }
  });

  return null;
}

7. Optimizing Fonts with next/font

Custom web fonts can cause text to flash (Flash of Unstyled Text - FOUT) or remain invisible (Flash of Invisible Text - FOIT) while the font file downloads.

The next/font module pre-downloads and self-hosts fonts locally during build time, generating custom fallback CSS variables to align layouts perfectly:

// app/layout.tsx
import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap', // Prevents FOIT by rendering fallback instantly
  variable: '--font-inter',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body className="font-sans">
        {children}
      </body>
    </html>
  );
}

8. Script Loading Optimization Strategies

Third-party script inclusion (like analytics tags, chat boxes, or cookie consent banners) can block page parsing, degrading LCP and INP. Next.js provides the next/script component to control script execution priority:

// components/ExternalScripts.tsx
import Script from 'next/script';

export default function ExternalScripts() {
  return (
    <>
      {/* Load immediately after page becomes interactive */}
      <Script
        src="https://example.com/analytics.js"
        strategy="afterInteractive"
      />

      {/* Defer script download completely to idle time */}
      <Script
        src="https://example.com/chat-widget.js"
        strategy="lazyOnload"
      />
    </>
  );
}

9. Automated Performance Audits with Lighthouse CI

Preventing performance regressions requires continuous testing. By adding Lighthouse CI to your GitHub Actions workflows, you can automatically run audits on every pull request before code is merged:

# .github/workflows/lighthouse.yml
name: Performance Audit

on: [pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install dependencies
        run: npm ci
        
      - name: Build production site
        run: npm run build
        
      - name: Run Lighthouse Audit
        uses: treosh/lighthouse-ci-action@v12
        with:
          urls: |
            http://localhost:3000/
            http://localhost:3000/courses
          uploadArtifacts: true # Save reports for debugging
          temporaryPublicStorage: true

10. Common Gotchas

  • Testing Vitals in Development Mode: Checking Lighthouse scores or console metrics while running next dev is inaccurate. Next.js development compilation adds extensive runtime debugging code and disables production optimizations, resulting in false negatives. Always audit performance on production builds: npm run build && npm run start.
  • Client-Side Layout Shifts from Hydration Mismatches: If your Server Component renders one set of data (e.g. server timestamp) and the Client Component overrides it immediately during client hydration, a severe layout shift can occur. Keep server and client renders identical on load.
  • Misuse of Image Sizes Attribute: The next/image component requires a sizes attribute for responsive images. Lacking this forces mobile browsers to download high-resolution desktop images, severely degrading LCP.

Key Takeaways

  • Core Web Vitals measure real-world performance across loading (LCP), stability (CLS), and interactivity (INP).
  • Preload hero images and prioritize above-the-fold content using the priority image attribute to improve LCP.
  • Reserve dimensions for media files and utilize next/font self-hosting structures to eliminate layout shifts (CLS).
  • Minimize execution blockages on the main thread during user interactions to lower INP values.
  • Implement useReportWebVitals to forward real-world user performance data to your logging services.

Optimizing performance and mastering technical SEO completes the foundations of advanced frontend engineering. Now, we must shift our focus to securing our applications and restricting private layouts. In the next module, we start our exploration of Authentication & Authorization with an Authentication Options Overview, mapping out authentication workflows and comparing OAuth, local credentials, and serverless authentication providers.

Chat with us