DevelopmentUpdated Jul 16, 2026

Shipping Core Web Vitals: a practical Next.js checklist

The exact steps we use to hit green LCP, CLS, and INP on every launch — with real before/after numbers, the code that matters, and a checklist you can copy into your next sprint.

Omar Haddad
Omar HaddadSenior Frontend Engineer · 8 min read
Developer reviewing performance metrics on a laptop

The three metrics that matter: LCP (loading, < 2.5s), CLS (visual stability, < 0.1), and INP (responsiveness, < 200ms). Everything below maps back to one of these.

Core Web Vitals stopped being a "nice to have" the moment Google folded them into ranking signals — but the real reason to care is simpler: fast sites make more money. Every 100ms we shave off load time nudges conversion up. Here's the exact process we run on every Next.js project, from first audit to a green scorecard in production.

Why Core Web Vitals actually matter

Field data beats lab data. Lighthouse gives you a controlled snapshot, but Google ranks on real-user metrics collected from the Chrome UX Report. That means your p75 — the experience of your slowest 25% of users — is what counts, not the number you see on a fast laptop over office Wi-Fi.

On a recent e-commerce replatform, moving LCP from 4.1s to 1.3s lifted organic sessions 22% and checkout conversion 18% inside a single quarter. Same catalog, same traffic — just faster.

The launch checklist

We treat performance as a definition-of-done, not a cleanup phase. These are the items that appear on every ticket board.

1. Tame your images

Images are the number-one LCP offender. Use next/image so you get automatic sizing, lazy-loading, and modern formats for free — then mark the hero image as priority so it never waits in the lazy queue:

import Image from "next/image";

export function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Product dashboard"
      width={1280}
      height={720}
      priority               // preload the LCP image
      sizes="(max-width: 768px) 100vw, 1280px"
    />
  );
}

2. Load fonts without layout shift

Web fonts cause CLS when text reflows on swap. next/font self-hosts and sizes fallbacks to match, so the jump disappears:

import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });

Pair that with font-display: swap and a metric-compatible fallback, and your text is readable instantly with zero shift.

3. Ship less JavaScript

INP lives and dies by main-thread work. Audit your bundle and be ruthless:

  • Prefer Server Components — send zero JS for anything that doesn't need interactivity.
  • Dynamically import heavy, below-the-fold widgets with next/dynamic.
  • Replace a 40KB date library with a 2KB helper you actually control.
  • Defer third-party scripts (analytics, chat) with the afterInteractive or lazyOnload strategy.

Measuring the right way

Ship a tiny reporter so you're watching field metrics, not guessing. Next.js exposes them through useReportWebVitals:

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

export function Vitals() {
  useReportWebVitals((metric) => {
    // send to your analytics endpoint
    navigator.sendBeacon("/api/vitals", JSON.stringify(metric));
  });
  return null;
}
Optimize what your users actually feel. A green lab score with a red field score means you tuned the wrong machine.
Analytics dashboard showing performance graphs
Track p75 for LCP, CLS, and INP over time — regressions are easiest to fix the week they ship.

Common pitfalls

  1. Chasing the Lighthouse number. It's a proxy. Optimize field data first.
  2. Hydrating everything. Interactive islands beat a fully-hydrated page.
  3. Unbounded third parties. One heavy embed can wreck INP no matter how tight your own code is.
  4. No performance budget in CI. Without a gate, wins quietly erode release by release.

Wrapping up

Fast is a feature. Bake these checks into your definition-of-done, watch field data instead of vanity scores, and put a budget in CI so gains stick. Do that and green Core Web Vitals stop being a scramble before launch — they become the default.


Omar Haddad
Written by

Omar Haddad

Senior Frontend Engineer at DevLabico. Obsessed with fast, accessible interfaces and shipping Next.js apps that stay green in production.

Discussion (2)

  • Nadia Cole· 2 days ago

    The priority tip alone fixed our LCP flag. We'd been lazy-loading the hero this whole time 🤦

  • Yusuf Adib· 5 days ago

    Adding a performance budget to CI was the missing piece for us. Great, practical write-up.

More articles are on the way — check back soon.