Back to blog
Performance
10 min read
Performance
Core Web Vitals

Core Web Vitals Optimization for React and Next.js Sites

A practical guide to optimizing LCP, INP, and CLS in Next.js and React applications — covering image optimization, code splitting, font loading, layout shift prevention, and how pre-built blocks can accelerate performance work.

SB

SERP Blocks Team

Product

Core Web Vitals Optimization for React and Next.js Sites cover

Core Web Vitals are the performance metrics Google uses to evaluate real-world user experience on your site. They directly influence search rankings, and they shape how visitors perceive your application the moment a page loads. For React and Next.js developers, understanding these metrics is not optional — it is a competitive requirement.

This guide covers the three Core Web Vitals in depth, explains why each one matters for Next.js specifically, and provides concrete fixes you can apply today. We also look at how choosing pre-built, performance-tested UI blocks can eliminate entire categories of performance problems before they start.

The Three Core Web Vitals in 2026

Google's Core Web Vitals consist of three metrics that measure loading performance, interactivity, and visual stability:

Largest Contentful Paint (LCP) measures how long it takes for the largest visible element — typically a hero image, heading, or video — to finish rendering. The target is under 2.5 seconds.

Interaction to Next Paint (INP) replaced First Input Delay in March 2024 and measures the latency of all interactions throughout the page lifecycle, not just the first one. The target is under 200 milliseconds.

Cumulative Layout Shift (CLS) measures unexpected layout movement during the entire lifespan of the page. The target is a score below 0.1.

Each of these metrics can be directly influenced by how you structure your Next.js application, which components you use, and how you handle assets.

Optimizing Largest Contentful Paint

LCP is usually the first metric that needs attention. In Next.js applications, the LCP element is often a hero image, a large heading rendered with a custom font, or a background video. The path to a fast LCP involves reducing the time between the initial request and the moment that element is fully painted.

Use the Next.js Image Component

The next/image component handles responsive sizing, lazy loading, and modern format conversion automatically. For your LCP image — typically the hero or above-the-fold visual — add the priority prop to disable lazy loading and trigger a preload hint:

import Image from "next/image";

export function HeroSection() {
  return (
    <section className="relative h-[600px] w-full">
      <Image
        src="/images/hero-background.jpg"
        alt="Product showcase"
        fill
        priority
        sizes="100vw"
        className="object-cover"
      />
      <div className="relative z-10 flex items-center justify-center h-full">
        <h1 className="text-5xl font-bold text-white">Ship Faster</h1>
      </div>
    </section>
  );
}

The priority prop adds a <link rel="preload"> tag to the document head, which tells the browser to fetch the image immediately rather than waiting for the rendering engine to discover it. This alone can shave 500ms or more off LCP on image-heavy pages.

Preload Critical Fonts

Custom fonts are a common LCP bottleneck. When the browser encounters text styled with a font that has not loaded yet, it either shows invisible text (FOIT) or falls back to a system font and repaints later (FOUT). Both scenarios delay LCP.

Next.js provides the next/font module, which automatically self-hosts fonts and adds preload hints:

import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-inter",
});

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

The display: "swap" setting ensures the browser renders text with a fallback font immediately, then swaps to the custom font once loaded. This prevents the font from blocking LCP entirely.

Server-Side Rendering and Streaming

Next.js App Router uses React Server Components by default, which means your components render on the server and send HTML to the browser. This is inherently faster for LCP than client-side rendering because the browser receives meaningful content immediately.

For pages with expensive data fetching, use React Suspense boundaries to stream content progressively:

import { Suspense } from "react";

export default function ProductPage() {
  return (
    <main>
      <HeroSection />
      <Suspense fallback={<ProductGridSkeleton />}>
        <ProductGrid />
      </Suspense>
    </main>
  );
}

The hero section renders immediately while the product grid streams in as the data becomes available. The LCP element — usually part of the hero — paints without waiting for slower downstream data.

Reduce Server Response Time

LCP cannot be fast if the server itself is slow. In Next.js, ensure your hosting environment supports edge rendering or at minimum provides CDN caching for static and ISR pages. Check that your revalidate intervals are set appropriately so that pages are served from cache rather than regenerated on every request.

Optimizing Interaction to Next Paint

INP measures how responsive your application feels during use. Every click, tap, and keyboard interaction is tracked, and the worst interaction (with some statistical smoothing) becomes your INP score. React applications are particularly vulnerable to poor INP because heavy re-renders can block the main thread.

Minimize Client-Side JavaScript

The biggest INP improvement in Next.js comes from using Server Components aggressively. Every component that does not need interactivity — headings, paragraphs, images, layout containers — should be a Server Component. Only add "use client" to components that genuinely require browser APIs, event handlers, or state.

This reduces the amount of JavaScript shipped to the browser, which directly reduces the work the main thread performs on interactions.

Debounce Expensive Event Handlers

Search inputs, filter controls, and form fields that trigger data fetching or heavy computation should debounce their handlers:

"use client";

import { useState, useDeferredValue } from "react";

export function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
  const [value, setValue] = useState("");
  const deferredValue = useDeferredValue(value);

  useEffect(() => {
    onSearch(deferredValue);
  }, [deferredValue, onSearch]);

  return (
    <input
      type="search"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Search products..."
      className="w-full rounded-md border px-4 py-2"
    />
  );
}

The useDeferredValue hook lets React prioritize keeping the input responsive while deferring the more expensive downstream rendering.

Avoid Layout Thrashing

Avoid reading layout properties (like offsetHeight or getBoundingClientRect) and then immediately writing to the DOM. This forces the browser to perform a synchronous layout recalculation, which blocks the main thread and inflates INP. Batch your DOM reads and writes separately, or use requestAnimationFrame to schedule writes.

Use React.lazy for Heavy Components

Components like rich text editors, chart libraries, or map widgets should be loaded lazily so they do not add to the initial bundle:

import dynamic from "next/dynamic";

const ChartWidget = dynamic(() => import("@/components/chart-widget"), {
  ssr: false,
  loading: () => <div className="h-[400px] animate-pulse rounded-lg bg-muted" />,
});

This keeps the main thread lighter during initial page interactions.

Preventing Cumulative Layout Shift

CLS measures visual stability. Every time an element shifts position unexpectedly — because an image loaded without reserved dimensions, a font swapped with different metrics, or a dynamic ad injected content above the fold — the CLS score increases.

Always Set Explicit Dimensions on Media

Images and videos without explicit width and height cause layout shifts when they load. The next/image component handles this automatically when you provide width and height props or use the fill prop with a sized container:

<div className="relative aspect-video w-full">
  <Image src="/images/feature.jpg" alt="Feature" fill className="object-cover" />
</div>

The aspect-video class from Tailwind CSS reserves the correct space in the layout before the image loads, eliminating the shift entirely.

Reserve Space for Dynamic Content

Any content that loads asynchronously — ads, embeds, cookie banners, notification bars — must have space reserved in advance. Use skeleton loaders with fixed dimensions:

function ProductCardSkeleton() {
  return (
    <div className="h-[320px] w-full animate-pulse rounded-lg bg-muted" />
  );
}

Match the skeleton dimensions exactly to the final rendered component. If your product cards are 320px tall, the skeleton should be 320px tall.

Handle Font Swap Shifts

Even with display: "swap", font loading can cause subtle layout shifts if the fallback font has different metrics than the custom font. Next.js 14+ supports automatic font metric adjustments through next/font, which generates size-adjust, ascent-override, and descent-override CSS properties to match the fallback font metrics to the custom font. This is enabled by default — just ensure you are using next/font rather than manually loading fonts via CSS @font-face rules.

Avoid Injecting Content Above Existing Content

A common CLS offender is a banner or notification bar that pushes the entire page down after the initial render. If you need a top-of-page banner, render it in the initial HTML with a fixed height rather than injecting it dynamically after the page loads.

How Pre-Built Blocks Eliminate Performance Problems

Every performance fix described above requires developer time and testing. You have to ensure images have dimensions, fonts load correctly, JavaScript is split properly, and interactive elements do not block the main thread. Multiply this by every page and every section on your site, and the performance work alone can consume weeks.

This is where pre-built UI blocks provide a structural advantage. When blocks are built with performance as a design constraint from the beginning, you inherit those optimizations automatically.

At SERP Blocks, our library of over 1,200 blocks across 55 categories is built on shadcn/ui and Tailwind CSS — both of which produce minimal, tree-shakeable CSS and zero runtime JavaScript overhead. Every block is a React Server Component by default unless interactivity is required.

Consider the scope of what this covers. Our 81 Hero blocks all use proper image handling with reserved aspect ratios, eliminating CLS from above-the-fold content. The 132 Feature section blocks use semantic HTML and efficient layouts that render server-side. The 110 Card blocks, 40 Testimonial blocks, and 33 Contact form blocks are all structured to minimize client-side JavaScript.

For e-commerce applications, the 25 Product blocks, 20 Product List blocks, 11 Shopping Cart blocks, and 15 Payment Form blocks handle complex interactive patterns while keeping bundle sizes small through proper code splitting. The 10 Order Confirmation blocks and 13 Reviews blocks render entirely server-side since they display static content.

Our 21 Pricing blocks, 32 CTA blocks, and 20 Banner blocks are optimized for above-the-fold placement where LCP and CLS matter most. The 21 FAQ blocks use native HTML <details> elements where possible, avoiding JavaScript-based accordion implementations that inflate INP scores.

Even specialized categories — 20 Dashboard blocks, 17 Table blocks, 13 Modal blocks, 20 Schedule blocks, and 10 Admin Sidebar blocks — are built with performance budgets in mind. Interactive components use client boundaries surgically, wrapping only the interactive elements rather than entire page sections.

With 60 free blocks available and a one-time Pro purchase for the full library, you can audit the performance characteristics yourself before committing.

A Practical Core Web Vitals Checklist

Use this checklist to audit your Next.js application:

LCP Checklist:

  • LCP image uses next/image with the priority prop

  • Fonts loaded via next/font with display: "swap"

  • Server response time under 600ms (check with server-timing headers)

  • No render-blocking CSS or JavaScript in the critical path

  • Above-the-fold content renders via Server Components

  • CDN caching enabled for static and ISR pages

INP Checklist:

  • Interactive components use "use client" only where necessary

  • Expensive event handlers are debounced or use useDeferredValue

  • Heavy third-party libraries loaded with dynamic() or React.lazy

  • No synchronous layout reads followed by DOM writes

  • Long tasks broken into smaller chunks with requestIdleCallback or scheduler.yield()

CLS Checklist:

  • All images have explicit width/height or use fill with a sized container

  • Fonts use metric overrides via next/font

  • Dynamic content (ads, banners, embeds) has reserved space via skeletons

  • No content injected above existing content after initial render

  • Web fonts loaded before or simultaneously with initial paint

Measuring Core Web Vitals in Next.js

Next.js provides built-in Web Vitals reporting through the useReportWebVitals hook:

"use client";

import { useReportWebVitals } from "next/web-vitals";

export function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    // Send to your analytics endpoint
    fetch("/api/vitals", {
      method: "POST",
      body: JSON.stringify({
        name: metric.name,
        value: metric.value,
        rating: metric.rating,
        navigationType: metric.navigationType,
      }),
    });
  });

  return null;
}

For real-user monitoring, the Chrome User Experience Report (CrUX) provides field data that reflects actual visitor experience. PageSpeed Insights and Google Search Console surface this data at the URL and origin level.

Lab tools like Lighthouse and Chrome DevTools provide controlled measurements for debugging, but always validate against field data. A page that scores well in Lighthouse but poorly in CrUX likely has issues that only appear under real-world conditions — slower devices, congested networks, or interaction patterns that lab tests do not simulate.

Conclusion

Core Web Vitals optimization in Next.js comes down to three principles: send less JavaScript, render meaningful content fast, and reserve space for everything that loads asynchronously. The framework provides the tools — Server Components, next/image, next/font, streaming, and ISR — but you have to use them deliberately.

Pre-built block libraries like SERP Blocks give you a head start by handling performance at the component level. When every hero section, feature grid, pricing table, and contact form in your application is already optimized for LCP, INP, and CLS, you can focus your performance budget on the custom logic that makes your product unique.

Start with the checklist above, measure with real-user data, and iterate. Performance is not a one-time fix — it is a discipline that compounds over time.

    Core Web Vitals Optimization for React and Next.js Sites | SERP BLOCKS