---
title: How to improve Core Web Vitals
description: Improve Core Web Vitals (INP, LCP, CLS) with metric-specific fixes, field-data measurement, and Next.js/Vercel performance best practices.
url: /kb/guide/how-to-improve-core-web-vitals
canonical_url: "https://vercel.com/kb/guide/how-to-improve-core-web-vitals"
published: 2025-11-03
last_updated: 2026-08-18
authors: Vercel
related:
  - /blog/demystifying-inp-new-tools-and-actionable-insights
  - /docs/cdn
  - /blog/a-better-developer-experience-makes-building-cruise-critic-more-efficient
  - /docs/speed-insights
  - /docs/vercel-toolbar/layout-shift-tool
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---
<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [Metrics](https://vercel.com/docs/speed-insights/metrics?from=related) — Learn what each performance metric on Speed Insights means and how the scores are calculated.
- [Analytics](https://nextjs.org/docs/app/guides/analytics?from=related) — Measure and track page performance using Next.js Speed Insights
- [Analytics](https://nextjs.org/docs/pages/guides/analytics?from=related) — Measure and track page performance using Next.js
- [Using with CLI](https://vercel.com/docs/speed-insights/accessing-metrics-with-vercel-cli?from=related) — Use the Vercel CLI to query Speed Insights metrics from your terminal.
- [useReportWebVitals](https://nextjs.org/docs/pages/api-reference/functions/use-report-web-vitals?from=related) — useReportWebVitals
- [Using Speed Insights](https://vercel.com/docs/speed-insights/using-speed-insights?from=related) — Learn how to use Speed Insights to analyze your application's performance data.
- [Pricing & Usage](https://vercel.com/docs/manage-cdn-usage?from=related) — Understand CDN pricing resources, monitor usage from your dashboard, and optimize Fast Data Transfer, Fast Origin Transf
- [useReportWebVitals](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals?from=related) — API Reference for the useReportWebVitals function.
- [Why site performance matters](https://vercel.com/kb/guide/why-site-performance-matters?from=related) — How site speed, Core Web Vitals, and SEO combine to drive user retention, conversion rates, and revenue growth.
- [First Input Delay \(FID\) vs. Interaction to Next Paint \(INP\)](https://vercel.com/kb/guide/first-input-delay-vs-interaction-to-next-paint?from=related) — Learn about the differences between FID and INP and how to optimize your website's INP score.
- [How to reduce ISR Writes by shrinking cached output](https://vercel.com/kb/guide/how-to-reduce-isr-writes?from=related) — Reduce ISR Write units by finding large changed routes and removing unnecessary data from their cached output.

Full cross-link map for this page: [/kb/guide/how-to-improve-core-web-vitals.graph.md](/kb/guide/how-to-improve-core-web-vitals.graph.md)
<!-- /docsgraph:related -->


Core Web Vitals optimization sprints often hit the same wall. Your Lighthouse scores show all green, the deployment ships smoothly, yet the real-world field data refuses to budge. The culprit dragging down your score is often Interaction to Next Paint (INP), which is the one metric that a standard Lighthouse lab audit cannot measure.

Foundational performance wins can compound across your stack, but each Core Web Vital ultimately breaks down for different reasons at different phases. Treating INP, LCP, and CLS, the metrics that make up Core Web Vitals, as three distinct bottlenecks that require three targeted action plans is the most reliable way to push a page past the 75th-percentile bar.

## What counts as a good Core Web Vitals score

Core Web Vitals are three field-data metrics that measure real-world user experience for loading performance, interactivity, and visual stability of the page. Google publishes the thresholds at [web.dev](http://web.dev). Largest Contentful Paint should occur within 2.5 seconds of the page starting to load, Interaction to Next Paint should stay under 200 milliseconds, and Cumulative Layout Shift should stay below 0.1.

Google assesses each Core Web Vital at the 75th percentile. Put simply, LCP, INP, and CLS must each be in the good range for at least 75% of visits. A page passes only when all three do.

Google's own framing is deliberately conservative. It says Core Web Vitals are used by its ranking systems, and that good Core Web Vitals, along with other page experience aspects, align with what its core ranking systems seek to reward. Content relevance and quality remain more important ranking factors than page experience signals, and passing Core Web Vitals will not catapult a page with mediocre content to position one in the search results. However, when two comparably relevant pages compete for the same query, page experience can be the rankings tiebreaker.

## Why Lighthouse and field data disagree

Lighthouse's standard audit only loads the page without interacting, so it produces no INP score. A page can even hit a perfect 100 lab score in PageSpeed Insights while the CrUX field panel on that same report shows failing Core Web Vitals.

Field data and lab data measure entirely different things. Field data, also called Real User Monitoring (RUM) data, comes from actual visitors using your site in real conditions. It captures variations in device capability, network speed, geographic location, and browsing behavior.

[Google's CrUX (Chrome User Experience Report)](https://developer.chrome.com/docs/crux) dataset collects field data from Chrome users who have opted in, and Google's Core Web Vitals assessment is based exclusively on field data.

Lab data comes from controlled tests in a fixed environment: an emulated device in Lighthouse's case, real devices in fixed locations in WebPageTest's. Lab tests are repeatable and useful for diagnosing specific issues, but they do not reflect the diversity of real user experiences.

INP exposes the main limitation of page-load lab testing. A standard Lighthouse audit loads the page but does not interact with it, so it cannot report an INP score. Lab tools can approximate INP with scripted interactions, but the result reflects only the interactions you choose to test.

In Lighthouse, the Total Blocking Time (TBT) metric correlates with INP but does not predict it because TBT cannot guess when real users will interact with a page, and a page can post a good TBT and still have poor INP.

## How to fix INP

When [Interaction to Next Paint (INP)](https://web.dev/articles/inp) is the metric holding a page back, the cause sits in one of three phases between the user's input and the next paint, and each phase takes a different fix.

INP captures every discrete interaction across the page lifecycle (clicks, taps, key presses), times each one from input through processing to the next paint, and reports the longest interaction, ignoring outliers. Field tools like CrUX then aggregate page visits at the 75th percentile.

Use attribution data from the [web-vitals library](https://github.com/GoogleChrome/web-vitals) to see where an interaction spent its time and which element the user interacted with. Start with the largest timing value. `inputDelay` means the main thread was busy before the handler started, `processingDuration` points to work in the handler, and `presentationDelay` points to slow rendering after it finishes.

**Input delay.** This accumulates when a long task (anything the browser does on the main thread for longer than 50 milliseconds) is mid-execution at the moment the user interacts. Input delay increases only when a long task overlaps with an interaction. Hydration that finished four seconds before a click adds nothing to the score, while a third-party analytics script that fires on every scroll does. To reduce input delay, free the main thread so it can start event handlers promptly. Defer non-critical scripts until after the page becomes interactive, break long tasks into smaller chunks, and yield between them with `scheduler.yield()`. It ships in Chromium and Firefox 142+, so use a `setTimeout` fallback in Safari.

**Processing duration.** Expensive DOM reads and writes inside an event handler increase processing duration. Reading a layout property immediately after writing to the DOM can trigger a forced synchronous layout. The browser has to then recalculate layout before the event handler can continue, which increases processing duration. To reduce processing duration, minimize the work an event handler performs before the browser can render the next frame. Batch DOM mutations, schedule visual updates with `requestAnimationFrame`, and move non-UI calculations to a web worker. For touch and scroll interactions, mark `touchstart`, `touchmove`, and `wheel` listeners as passive when they do not call `preventDefault()`. The browser can then start scrolling without waiting for the listener.

**Presentation delay.** Large DOM trees and complex CSS selectors can slow the rendering pipeline, increasing the time between an event handler finishing and the next paint. Applying `content-visibility: auto` lets the browser skip rendering work for off-screen content while keeping it available to find-in-page and the accessibility tree. Animating with `transform` and `opacity` avoids triggering layout or paint, so those animations run on the compositor thread, and `will-change` tells the browser about likely upcoming animations ahead of time.

For React apps, a slow next paint can come from a large React re-render after an interaction. React 18 can pause and resume non-urgent rendering, and `useTransition` lets you mark a state update as non-urgent so React can prioritize the response the user is waiting for. These tools control React’s rendering work only. If an `onClick` handler synchronously flushes analytics or runs an expensive third-party script, reduce or defer that work in the handler itself. Moving to the App Router alone will not improve it.

The 23% of mobile websites that miss "good" INP per the [2025 Web Almanac](https://almanac.httparchive.org/en/2025/performance) trace back to all three phases. [To get an in depth walkthrough of INP debugging workflows, review this guide.](https://vercel.com/blog/demystifying-inp-new-tools-and-actionable-insights)

## How to fix LCP

[Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) has four stages, and the slowest one tells you which fix to prioritize. Time to First Byte (TTFB) is the server response time. Resource load delay is the gap between TTFB and when the browser starts fetching the LCP resource. Resource load duration is how long that resource takes to download. Element render delay is the time from download completion to the element appearing on screen.

Most LCP fixes start the LCP resource download earlier, shorten the download, or remove the work that delays the element’s final render.

**Fetch the LCP resource earlier.** The LCP resource is often discovered late because the browser must first download HTML, parse CSS, and find the image reference. A `preload` hint in the document head starts the image fetch immediately, in parallel with CSS and JavaScript, and `fetchpriority="high"` upgrades its priority. Together they can shrink resource load delay materially.

**Unblock rendering.** External CSS stylesheets are render-blocking by default, so the browser will not paint until the blocking CSS in the head is downloaded and parsed (only stylesheets whose media attribute matches the current environment block rendering). Inline the critical CSS for above-the-fold content directly in the HTML and load the rest asynchronously, which pays off most on slower mobile connections where CSS downloads take hundreds of milliseconds.

**Shrink the image.** AVIF compresses roughly 50% better than JPEG but carries slightly less browser coverage than WebP. Use AVIF when a browser supports it, WebP as the next fallback, and JPEG as a final fallback. A `<picture>` element lets the browser choose the first format it supports. Next.js's [Image component](https://nextjs.org/docs/app/api-reference/components/image) handles the same negotiation server-side using the browser's `Accept` header, applies responsive sizing, and lazy-loads below-the-fold images, so a single component covers the LCP image and CLS prevention via dimension reservation.

When LCP improves, conversion rate does as well. In a [2021 case study](https://web.dev/case-studies/vodafone), Vodafone improved their Largest Contentful Paint by 31% and saw an 8% increase in sales, a 15% improvement in lead-to-visit rate, and an 11% improvement in cart-to-visit rate.

## How to fix CLS

CLS reflects the largest burst of unexpected layout shifts (a session window capped at 5 seconds, with shifts less than a second apart) rather than a lifetime sum, and most of that burst usually comes from a handful of large shifts you can prevent.

The score is derived by multiplying two factors for every unexpected layout shift. The impact fraction is the portion of the viewport that was shifted during the layout change. The distance fraction is the distance the unstable elements moved, relative to the viewport's largest dimension.

Suppose a banner pushes the whole viewport down by 22% of its height. The shift has a distance fraction of 0.22 and an impact fraction of 1.0, producing a CLS score of 0.22. That single shift exceeds the 0.1 threshold for a good CLS score. Because the score multiplies impact by distance, one large late shift can blow the budget while small mid-page shifts contribute little.

Layout shifts within 500 milliseconds of a user interaction are excluded from the calculation, because intentional shifts (a menu opening, a form expanding) should not count against the score.

The most common causes of CLS are images without explicit dimensions, dynamically injected content like ads or cookie banners, and web fonts that swap in at a different size than the fallback.

To prevent CLS, give the browser a stable layout before media, fonts, or client-side content change the page. The fixes below focus on reserving space, matching font metrics, and preventing layout changes during hydration.

**Reserve space for visual content.** When images and videos load without `width` and `height` attributes, the browser cannot reserve space for them, and everything below shifts down once the media loads. Always include `width` and `height` on `<img>`, `<video>`, and `<iframe>` elements, or use CSS `aspect-ratio` for responsive images. The Next.js Image component infers dimensions automatically for statically imported images and reserves space when you pass `width` and `height` or the `fill` prop, making dimension reservation a default. For content whose size is unknown at render time, such as a third-party widget or injected banner, a sensible `min-height` keeps the shift bounded as the container grows.

**Stabilize web fonts.** With `font-display: swap`, text renders in a fallback font and re-renders when the custom font loads; if the metrics differ, the swap reflows surrounding content. Match the fallback to your custom font with the CSS override descriptors (`size-adjust`, `ascent-override`, `descent-override`, and `line-gap-override`) so both occupy the same vertical space and the swap becomes invisible. Next.js's [Font module](https://nextjs.org/docs/app/api-reference/components/font) automates the preloading and the metric matching at build time, which in practice prevents the swap shift.

**Prevent hydration layout mismatches.** In React and Next.js applications, CLS spikes when the server-rendered HTML differs from what the client renders on hydration. Using `window.innerWidth` in a `useEffect` to toggle mobile or desktop navigation snaps from one view to the other after hydration, and rendering `null` on the server for an authenticated component pushes the page down when the client injects it. Rely on pure CSS (`@media` queries) to toggle responsive components so both DOM nodes are present in the initial HTML, and render a Skeleton component of the same dimensions on the server so the space is reserved before client data arrives.

**Restore previously visited pages from bfcache.** When users navigate back or forward, the browser can restore the previous page from a memory snapshot instead of rebuilding it. Because the layout is already final, restoring the page from `bfcache` does not introduce new layout shifts. Pages become ineligible when they attach `unload` event listeners (in desktop Chrome and Firefox); pages served with `Cache-Control: no-store` were historically excluded too, though Chrome now stores them when it is safe to do so. Use Chrome DevTools to test `bfcache` eligibility before release. For real-user traffic, the Not Restored Reasons API identifies what prevented the browser from restoring a page from cache.

## Where infrastructure shifts the LCP math

Time to First Byte (TTFB) is the delay before the browser receives the initial HTML. Until that HTML arrives, the browser cannot discover or request the LCP resource. If a transcontinental request takes 200ms to return the first byte, A 200ms TTFB delays LCP before the browser can even discover the LCP resource. Serving requests from a regional CDN can often reduce that TTFB to 50ms. Some CDNs use private backbone routing, which sends traffic between the provider’s locations over private network links rather than relying entirely on public internet routes. Removing 150ms at this stage speeds every later step, including preloading, image delivery, and rendering.

[Vercel CDN](https://vercel.com/docs/cdn) operates 126+ global points of presence with private backbone routing to 20+ compute-capable regions, and it is framework-aware, meaning it reads your routing, caching, and rendering configuration at build time and applies the right edge behavior automatically. For dynamic routes where TTFB is dominated by server work rather than network distance, [Fluid compute](https://vercel.com/fluid) runs that work in-region near your data, so server-rendered responses start streaming sooner.

Shopify and WordPress now include built-in speculative loading, which can begin fetching a likely next page before the user clicks in supported browsers. Since WordPress 6.8, WordPress Core has enabled conservative speculative prefetching by default for logged-out visitors on sites, while Shopify uses a more aggressive moderate configuration for Liquid storefronts. Shopify reports that this newer configuration reduced [median desktop LCP by 228ms](https://performance.shopify.com/blogs/blog/faster-storefront-navigations-with-moderate-speculation-rules) compared with its earlier configuration.

In a [2023 case study](https://vercel.com/blog/a-better-developer-experience-makes-building-cruise-critic-more-efficient), Cruise Critic reported an 85% reduction in page download time and a nearly 100% reduction in page size after moving to Next.js and Vercel. The site served roughly six million monthly visitors at the time.

Page download time is not a Core Web Vital, so this result does not by itself prove that LCP improved. It shows how infrastructure can improve LCP. Faster delivery of the initial HTML lowers TTFB, while smaller page resources can shorten the download of the LCP element.

## Measuring Core Web Vitals in production

Google assesses Core Web Vitals using field data from real visits, not lab tests. After you deploy a fix, a RUM tool shows whether it improved the experience for real users. CrUX then confirms the values Google uses for its assessment once the 28-day dataset updates.

Use lab tests to diagnose a bottleneck, RUM to see how a fix affects real visitors, and CrUX to verify the data Google uses for its assessment. The table below compares the main options by data type, whether they provide real-user CWV data, and what each one is best for.

| Tool                                                            | Data Type      | Real-user CWV data | Best For                                                   |
| --------------------------------------------------------------- | -------------- | ------------------ | ---------------------------------------------------------- |
| Lighthouse                                                      | Lab only       | No                 | Diagnosing specific performance issues                     |
| PageSpeed Insights (lab panel)                                  | Lab            | No                 | Quick "what would this score" check during development     |
| `web-vitals` library (in-app)                                   | Field          | Yes                | Reporting CWV from your own app to your own analytics      |
| CrUX Dashboard                                                  | Field (Google) | Yes                | The dataset Google uses for ranking; 28-day rolling window |
| PageSpeed Insights (field panel)                                | Field (CrUX)   | Yes                | Single-URL CrUX read with lab-side diagnostics attached    |
| [Vercel Speed Insights](https://vercel.com/docs/speed-insights) | Field (RUM)    | Yes                | Per-route, per-element, per-percentile attribution         |

Each source supports a different step in the investigation. Use Lighthouse or the PageSpeed Insights lab panel to reproduce and diagnose a suspected issue. Check CrUX to confirm whether it appears in Google’s field data. Then use RUM to identify the affected route or interaction in your own traffic.

"Real-user CWV data" indicates whether a source measures Core Web Vitals from actual visitors. CrUX is Google’s field dataset, while RUM tools collect comparable measurements from your own visitors. Lighthouse and the PageSpeed Insights lab panel can help diagnose LCP and CLS, but neither can tell you whether a page passes INP. Google calculates that assessment from INP recorded during real user visits. CrUX runs across millions of real visits but reports a rolling 28-day aggregation, updated daily, at the origin or URL level. Vercel Speed Insights tracks the three Core Web Vitals (LCP, INP, CLS) alongside First Contentful Paint, Time to First Byte, and First Input Delay across every deployed environment, filterable by route, device type, time range, percentile, and country, with element-level attribution that names the specific DOM target dragging INP.

Element-level attribution narrows a site-wide INP score to the interaction that needs investigation. An INP of 240ms tells you that users encounter a slow response, but not which code causes it. If RUM attributes the score to the search-suggestions handler on `/products`, the team knows where to start debugging.

Teams that run their own collection pipeline can use the `web-vitals` attribution build for similar data, while Speed Insights provides that attribution as a managed service.

## Build Core Web Vitals into every release

Treat Core Web Vitals as part of the delivery process. Use field data to identify which metric is failing, diagnose the cause with lab tests and RUM, validate the fix before release, and confirm the result with production data.

Preview Deployments give teams a place to perform that validation before a change reaches users. [Vercel Toolbar’s Layout Shift tool](https://vercel.com/docs/vercel-toolbar/layout-shift-tool) detects layout shifts, identifies the affected element, explains what changed, and replays the shift. Running it during pull request review helps teams catch CLS regressions before merge.

Apply the same workflow to every Core Web Vital. Measure real-user performance, trace the regression to its source, verify the fix, and continue monitoring after release.

## Frequently asked questions

**What's a good Core Web Vitals score?**

A page passes when 75% of page visits fall inside the "good" threshold for all three metrics: LCP at 2.5 seconds or less, INP at 200 milliseconds or less, and CLS at 0.1 or less. Per the 2025 Web Almanac data referenced earlier, only 48% of mobile websites and 56% of desktop websites currently pass all three.

**Does Lighthouse measure Core Web Vitals?**

Yes, but only in a lab setting, which is not what Google actually uses for search rankings. Lighthouse simulates LCP and CLS, but because its standard audit loads the page without interacting, it returns no INP score. A perfect Lighthouse score does not guarantee you will pass field measured Core Web Vitals, and a low score doesn't necessarily mean you are failing. The best approach is to use Lighthouse to diagnose specific bottlenecks, while relying on a Real User Monitoring (RUM) tool or the Chrome User Experience Report (CrUX) to measure your actual field performance.

**How long do Core Web Vitals improvements take to show up?**

CrUX operates on a 28-day rolling window. A fix shipped today rolls into the dataset gradually as new visits accumulate, so most improvements take roughly a month to fully register. RUM tools surface the change in real time, which is useful for verifying a fix landed before the CrUX dashboard catches up.

**Should you optimize for mobile or desktop first?**

Mobile first, almost always. The 2025 Web Almanac shows 48% of mobile websites pass all three Core Web Vitals compared to 56% of desktop sites, and the biggest gap is INP, where mobile lags desktop by 20 percentage points. Mobile devices have less CPU, less memory, and slower networks, and that gap is where most CWV regressions surface. Pages that pass mobile usually pass desktop without further work; the reverse is rarely true.

**What replaced First Input Delay?**

INP replaced First Input Delay (FID) as the responsiveness ranking signal. FID measured only the delay before the browser began processing the very first interaction on a page. INP captures every interaction throughout the page lifecycle and reports the longest, ignoring outliers, with field tools aggregating page visits at the 75th percentile, making it harder to game and far more representative of real user experience.