Core Web Vitals: How to Measure, Diagnose, and Fix Every Issue
Core Web Vitals became a Google ranking signal in 2021 and have grown in importance with every update since. They measure three aspects of page experience: loading speed, interactivity, and visual stability. All three need to be in the "Good" range to earn the page experience signal.
The Three Metrics
| Metric | Measures | Good | Needs Improvement | Poor |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Loading speed | ≤ 2.5 s | 2.5–4.0 s | > 4.0 s |
| INP (Interaction to Next Paint) | Responsiveness | ≤ 200 ms | 200–500 ms | > 500 ms |
| CLS (Cumulative Layout Shift) | Visual stability | ≤ 0.1 | 0.1–0.25 | > 0.25 |
INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. It's a more complete measure of responsiveness because it captures all interactions during the page lifecycle, not just the first one.
Where to Measure
Field Data (Real Users)
- Google Search Console → Core Web Vitals — shows page groups failing in the real world
- Chrome User Experience Report (CrUX) — 28-day rolling data from Chrome users
- PageSpeed Insights — combines lab + field data for a single URL
Lab Data (Simulated)
- Lighthouse (built into Chrome DevTools) — consistent, reproducible scores
- WebPageTest — detailed waterfall, filmstrip, multi-location testing
Always prioritise field data. Lab scores are useful for diagnosis but your ranking is determined by what real users experience.
LCP: Diagnosing and Fixing Slow Load
LCP is almost always the most impactful metric to improve. The LCP element is typically:
- A hero image
- A large text block
- A video poster image
Common LCP Causes and Fixes
Slow server response (TTFB)
The single biggest lever. If your server takes 800 ms to respond, your LCP can never be under 2.5 s.
Fixes:
- Use a CDN (Cloudflare, Fastly) to serve cached responses from edge nodes
- Enable HTTP/2 or HTTP/3 on your server
- Use ISR or static generation in Next.js instead of server-rendering on every request
Unoptimised LCP image
<!-- Bad: browser discovers image late, then fetches it -->
<img src="/hero.jpg" />
<!-- Good: preload hint tells browser immediately -->
<link rel="preload" as="image" href="/hero.jpg" fetchpriority="high" />
<img src="/hero.jpg" fetchpriority="high" />
In Next.js, use priority on the above-the-fold <Image> component:
<Image src="/hero.jpg" alt="Hero" fill priority />
Render-blocking resources
CSS and synchronous JS in <head> delay LCP. Audit with the Lighthouse "Eliminate render-blocking resources" diagnostic.
INP: Diagnosing and Fixing Poor Responsiveness
INP measures the time from user input (click, tap, keypress) to the next frame being painted. High INP is caused by long tasks on the main thread.
Diagnose with Chrome DevTools
- Open DevTools → Performance panel
- Record while interacting with the page
- Look for long tasks (red corner in the flame chart) during interactions
Common INP Causes and Fixes
Heavy event handlers
Break up synchronous work:
// Bad: 300 ms of synchronous work on click
button.addEventListener("click", () => {
doHeavyCalculation(); // blocks the main thread
updateUI();
});
// Good: yield to the browser between tasks
button.addEventListener("click", async () => {
updateUI(); // immediate visual feedback
await scheduler.yield(); // yield before heavy work
doHeavyCalculation();
});
Third-party scripts
Analytics, chat widgets, and ad scripts often run expensive tasks during interactions. Load them with defer or after user interaction:
<script src="analytics.js" defer></script>
Large React re-renders
Use React.memo, useMemo, and useCallback to prevent unnecessary re-renders on interaction.
CLS: Diagnosing and Fixing Layout Shifts
CLS is caused by elements moving after they've been painted. The most common culprits:
Images Without Dimensions
<!-- Causes CLS: browser doesn't know height until image loads -->
<img src="/photo.jpg" alt="Photo" />
<!-- Prevents CLS: space reserved before image loads -->
<img src="/photo.jpg" alt="Photo" width="800" height="450" />
In Next.js, <Image> requires width and height (or fill with a sized container), so this is handled automatically.
Ads and Embeds Without Reserved Space
Always give ad slots a minimum height:
.ad-slot {
min-height: 250px; /* reserve space before ad loads */
}
Web Fonts
Use font-display: optional or swap and preload your critical fonts:
<link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin />
Prioritisation Framework
Not all pages matter equally. Focus effort in this order:
- Homepage and landing pages — highest traffic, direct impact on conversions
- Top-10 organic traffic pages — already ranking; CWV improvement can push them higher
- Product/category pages (e-commerce) — directly tied to revenue
- Blog posts — usually already fast if images are optimised
Tracking Progress
Set up a baseline before making changes:
- Export current CWV data from GSC
- Make one change at a time
- Wait 28 days for CrUX data to update
- Compare field data before and after
Core Web Vitals improvement is cumulative — no single fix gets you from red to green. Stack the wins: faster hosting, optimised images, deferred scripts, reserved ad space. Each one moves the needle.