Core Web Vitals are Google's metrics for real-world user experience: how fast the main content appears, how quickly the page responds to input and how stable the layout is. They feed into Google's ranking systems and, more importantly, they correlate with conversion: a store that reacts slowly to a tap on "Add to cart" loses customers regardless of SEO. Since March 2024 the responsiveness metric is Interaction to Next Paint (INP), and it is the one most sites struggle with. This guide explains the three metrics, how to find problems with field data, and the fixes that work in React and Next.js applications.
The three Core Web Vitals
Each metric is assessed at the 75th percentile of page loads, separately for mobile and desktop (web.dev: Web Vitals):
| Metric | Measures | Good | Poor |
|---|---|---|---|
| LCP — Largest Contentful Paint | When the largest image or text block renders | ≤ 2.5 s | > 4 s |
| INP — Interaction to Next Paint | Delay from user input to the next frame, across all interactions | ≤ 200 ms | > 500 ms |
| CLS — Cumulative Layout Shift | Unexpected movement of visible content | ≤ 0.1 | > 0.25 |
Google's documentation states that Core Web Vitals are used by its ranking systems and recommends achieving good values for success with Search and for user experience generally (Google Search Central: Core Web Vitals). They are not a magic ranking switch — relevance still dominates — but between comparable pages, experience matters.
Why INP replaced FID
First Input Delay measured only the delay before the browser started handling the first interaction. Most sites passed it easily while still feeling sluggish. INP became a Core Web Vital on March 12, 2024 (web.dev: INP is a Core Web Vital). It observes all clicks, taps and key presses during a visit and reports one of the slowest, measuring the full path (web.dev: INP):
- Input delay — waiting for the main thread to be free, often because of long tasks.
- Processing duration — running your event handlers.
- Presentation delay — rendering and painting the next frame.
A slow INP can come from any of the three, so the fix depends on where time goes.
Step 1: Use field data, not just Lighthouse
A standard Lighthouse audit loads the page on one simulated device without interacting with it, so it cannot measure INP; Total Blocking Time is only a rough lab proxy. Use real-user data:
- Search Console's Core Web Vitals report groups URLs with similar issues.
- PageSpeed Insights shows Chrome User Experience Report (CrUX) data for a URL or origin (PageSpeed Insights; CrUX).
- Your own RUM. The
web-vitalslibrary reports metrics from your users, with attribution that tells you which element and which phase caused a slow interaction (web-vitals on GitHub).
import { onINP, onLCP, onCLS } from "web-vitals/attribution"
function send(metric: { name: string; value: number; attribution?: unknown }) {
navigator.sendBeacon("/api/vitals", JSON.stringify(metric))
}
onINP((m) => send({ name: m.name, value: m.value, attribution: {
target: m.attribution.interactionTarget, // which element was clicked
inputDelay: m.attribution.inputDelay,
processing: m.attribution.processingDuration,
presentation: m.attribution.presentationDelay,
}}))
onLCP(send)
onCLS(send)
With attribution data, "INP is 380 ms" becomes "the filter dropdown on category pages spends 300 ms in processing" — something a developer can fix.
Step 2: Fix INP
Break up long tasks
Any task over 50 ms blocks input. Split work and yield to the main thread so the browser can respond in between (web.dev: optimize long tasks). The scheduler.yield() API is designed for this, with a fallback where it is not supported (MDN: Scheduler.yield):
async function yieldToMain() {
if ("scheduler" in globalThis && "yield" in (globalThis as any).scheduler) {
return (globalThis as any).scheduler.yield()
}
return new Promise((resolve) => setTimeout(resolve, 0))
}
async function applyFilters(items: Item[]) {
updateFilterButtonState() // visible feedback first
await yieldToMain() // let the browser paint
const result = expensiveFilter(items)
renderResults(result)
}
Give feedback first, compute later
Update the pressed button, show a spinner or optimistic state immediately, then do the heavy work. In React, mark non-urgent updates as transitions so typing and clicks stay responsive while results re-render (React: useTransition).
Reduce work in event handlers
- Debounce input-driven searches and avoid recomputing derived data on every keystroke.
- Move heavy computation such as parsing or sorting large lists to a Web Worker.
- Avoid layout thrashing: don't read layout (
offsetHeight) and write styles in alternating loops.
Shrink rendering cost
- Large DOMs make every update expensive. Virtualize long lists and paginate tables.
- Use
content-visibility: autofor off-screen sections. - Audit third-party scripts — chat widgets, tag managers, A/B testing tools are frequent INP offenders. Load them late or remove what nobody uses.
The React Compiler, stable in Next.js 16, removes many unnecessary re-renders automatically; see Next.js 16 in production.
Step 3: Fix LCP
Break LCP into time to first byte, resource load delay, resource load time and render delay (web.dev: LCP). The usual fixes:
- Serve HTML fast. Static generation or partial prerendering, CDN caching and a fast backend.
- Make the LCP image discoverable early. Use a real
<img>in the initial HTML, not a CSS background or a client-rendered component; addfetchpriority="high"and never lazy-load the hero image. In Next.js,next/imagewithprioritydoes this. - Right-size images and use modern formats like AVIF or WebP.
- Avoid render-blocking resources: inline critical CSS, defer non-critical JavaScript, preload key fonts.
Step 4: Fix CLS
Layout shifts come from content that changes size after rendering (web.dev: CLS):
- Always set
widthandheight(oraspect-ratio) on images, videos and iframes. - Reserve space for ads, embeds and cookie banners.
- Load web fonts with
font-display: optionalor matched fallback metrics to avoid text reflow. - Never insert content above existing content unless it is a response to user input.
A workflow that keeps vitals green
- Collect RUM data with attribution on every page template.
- Set budgets: INP under 200 ms, LCP under 2.5 s, CLS under 0.1 at p75 on mobile.
- Review the worst templates weekly and fix the top offender.
- Add a Lighthouse CI check for LCP and CLS regressions in pull requests; it cannot replace field INP data, but it catches obvious problems early.
- Track vitals alongside business metrics — conversion, bounce, add-to-cart rate.
Performance is one part of technical SEO; the rest — metadata, sitemaps, hreflang and structured data — is covered in Technical SEO for Next.js. If you plan to make the site installable, compare the options in PWA vs native app.
Common INP culprits in React and Next.js apps
- Large client components re-rendering on every keystroke, such as a search box that filters a list of thousands of items synchronously.
- Hydration of big pages that blocks the main thread right when users start tapping; move static parts to Server Components.
- Heavy analytics and tag managers firing synchronous work on every click.
- State libraries updating the whole tree for a small change; split state and memoize, or let the React Compiler do it.
- Expensive
onChangevalidation on forms; validate on blur or debounce.
FAQ
Do Core Web Vitals directly affect rankings? Google uses them in its ranking systems, but relevance and content quality matter more. Good vitals help most when competing pages are similar.
Why is my Lighthouse score 95 but INP is poor? A standard Lighthouse audit doesn't interact with the page, so it cannot measure INP. Field data from real users is the source of truth.
Which devices matter most? Mobile, especially mid-range Android phones, where CPU limits make long tasks much longer than on a developer's laptop.
How long until improvements show in Search Console? CrUX data covers the previous 28 days, so expect several weeks before reports reflect a fix.
Do Core Web Vitals matter for B2B dashboards behind a login? Not for search rankings, since those pages aren't indexed, but they matter just as much for user productivity and satisfaction.
Sources
- web.dev. Web Vitals, Interaction to Next Paint, LCP, CLS.
- web.dev (2024). Interaction to Next Paint becomes a Core Web Vital on March 12.
- web.dev. Optimize long tasks and Optimize INP.
- Google Search Central. Understanding Core Web Vitals and Google search results.
- Google Chrome. web-vitals library and Chrome UX Report.
- MDN. Scheduler.yield().
- React. useTransition.