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
afterInteractiveorlazyOnloadstrategy.
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.
Common pitfalls
- Chasing the Lighthouse number. It's a proxy. Optimize field data first.
- Hydrating everything. Interactive islands beat a fully-hydrated page.
- Unbounded third parties. One heavy embed can wreck INP no matter how tight your own code is.
- 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.
Discussion (2)
- Nadia Cole· 2 days ago
The
prioritytip 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.