How to improve Core Web Vitals, and prove each fix worked
Published on November 11, 2026 by Mattias Geniar
A score of 78 says a page needs work. It doesn't say which fix to make first, or how you'll know it worked. The failing metric does. This post walks through how to improve Core Web Vitals in that order: start from the failed metric, change one thing, then watch the signal that proves it landed. We measure the lab side with our Lighthouse check.
What Core Web Vitals are, and where the numbers come from #
Core Web Vitals are three measures of how a page loads, responds, and holds still for real visitors, judged at the 75th percentile of real page views.
| Metric | What it measures | Pass threshold at the 75th percentile |
|---|---|---|
| Largest Contentful Paint (LCP) | How fast the biggest visible element loads | 2.5 seconds or less |
| Interaction to Next Paint (INP) | How fast the page responds to an interaction | 200 milliseconds or less |
| Cumulative Layout Shift (CLS) | How much visible content jumps during load | 0.1 or less |
Every fix below targets one row, and every section ends with the signal that shows whether it moved.
Lab data vs field data
Lighthouse measures in the lab: a controlled page load, with no visitor and no click. Core Web Vitals pass or fail on field data, from real visitors, at the 75th percentile. A lab run can improve the day you deploy; the field number moves only after enough real visits.
Oh Dear shows the lab side, and shows Total Blocking Time as the lab proxy for INP, because a lab tool with no user input can't measure INP. The field INP number lives in Search Console and PageSpeed Insights. If you want to take a deeper look into the mechanics, read how Lighthouse monitoring works.
Start from the metric that failed, not the score #
Four scores and six lab metrics can move at once, and a single score hides which one matters. Open the Lighthouse report, find the metric over its threshold, and start there: Oh Dear's Lighthouse monitoring feature flags a regression against per-metric thresholds you set.
Our default thresholds sit where Google's "poor" range starts: 4 seconds for LCP, 600 milliseconds for Total Blocking Time, and 0.25 for CLS. That catches a page that's clearly broken. If you want an alert as soon as a page drops out of "good", lower them to 2.5 seconds for LCP, 200 milliseconds for Total Blocking Time, and 0.1 for CLS.
The order of work
Fix loading first, then stability, then responsiveness: LCP first, CLS second, INP third. The loading fix changes the other two numbers, so another order means re-measuring the same page three times, unsure which change did what. One metric, one change, one signal.
How to improve LCP: the biggest element, and the server phase behind it #
Largest Contentful Paint is the moment the biggest visible element, often a hero image or headline, finishes rendering. When it fails, the page feels slow even if the rest loads quickly. Most LCP failures come from a small set of causes, which is also where improving website speed stops being abstract.
The four LCP timings
LCP is built from four parts: time to first byte, resource load delay, resource download time, and render delay. Time to first byte is the server wait; load delay is the gap before the browser starts the LCP resource; download time is its arrival; render delay is the wait before it's painted. Find which part is large and you've found the fix.
Make the LCP resource discoverable and prioritized
The browser can't prioritize a resource it hasn't found. Preload the hero asset, add fetchpriority="high" to the LCP image, and lazy-load everything below the fold, but never the LCP image itself. Use modern formats and responsive sizes, and cut render-blocking CSS and JavaScript ahead of the hero. If the hero is already prioritized and LCP is still slow, the wait is usually server-side. Our FAQ on why a site has poor performance lists the usual suspects.
When the wait is server-side
Oh Dear times the request in five phases: DNS lookup, TCP connection, TLS handshake, server processing, and content download. A long DNS phase points at name resolution; long TCP or TLS phases point at the connection path; a long server-processing phase points at the origin server or a missing cache; a long download phase points at a large payload. Understand how the performance check measures each phase and fix the phase over budget before optimizing the image, or the image work won't move LCP.
Signal that proves it worked: the LCP lab metric back under its threshold, and the response-time phases staying inside budget after the deploy.
How to improve INP: what to change on the main thread #
Interaction to Next Paint measures how quickly the page responds after a visitor clicks, taps, or presses a key. The work happens on the main thread, where every script competes. Keep in mind that INP is field data, so the field number lives outside of Oh Dear. We show Total Blocking Time in the lab, with a 600 millisecond default threshold, as the proxy to watch before the field number moves.
Find the slow interaction
Start with field data: Search Console's Core Web Vitals report and PageSpeed Insights show which pages and page types fail INP. To find the interaction itself, use real-user data, for example from the attribution build of Google's web-vitals library. Then diagnose in the lab: record the slow interaction in the Chrome DevTools Performance panel and read the long tasks behind it. Don't fix a fast interaction just because it's easy to reach.
Break up long tasks
A long task blocks the main thread and delays the next paint. Yield often, split long work into smaller pieces, and defer JavaScript that isn't needed for the first interaction. Avoid layout thrashing, where reading and writing layout in a loop forces repeated recalculation. We see this a lot with custom SVGs that together build fancy graphics. The browser has to render hundreds of those, and that's hard work for slower devices.
Audit third-party scripts one by one
Chat widgets, consent banners, and tag managers are where INP regressions usually hide. Each runs on the main thread, often before the visitor has done anything. Audit them one by one, delay what isn't essential, and remove what no longer earns its place.
Shrink the DOM and render off-screen content lazily
A smaller DOM means less work per interaction. Remove unused nodes, and use content-visibility: auto for off-screen sections so the browser can skip rendering them until they approach the viewport.
Signal that proves it worked: Total Blocking Time back under the 600 millisecond default in the lab, and the field INP number in Search Console moving at the next update. Don't call the fix done until the field number follows.
How to improve CLS: reserving the space, and the deploys that undo it #
Cumulative Layout Shift adds up how much visible content moves unexpectedly while the page loads. The fix is often simple: reserve space before content arrives, and never move content that's already on screen.
Images and embeds
Give every image and embed explicit width and height, or an aspect-ratio box, so the browser reserves that space before the file arrives. Reserve space for late-loading ads and video embeds, and place late content lower in the viewport where a shift hurts less. We usually don't recommend having videos above the fold, because they slow down the page load.
Fonts and animations
Web fonts can swap in late and change text width, shifting the lines around them. Preload only the fonts the hero needs, and either use font-display: optional or match the fallback font's metrics (with size-adjust) so the swap doesn't move anything. Animate transforms and opacity, not layout properties such as width, height, and top. Even then, try to load as few fonts as possible.
Load shifts vs post-load shifts
Separate shifts during load from shifts after load. Load shifts usually mean missing dimensions or late fonts; post-load shifts usually mean content injected after the first paint. Never inject content above rendered content without a user interaction.
The deploys that undo it
The fix is undone by the next deploy, not the first one. Oh Dear's Lighthouse check uses a two-run rule by default: a threshold must be crossed in two runs in a row before it notifies you, and it sends a recovery notification once the scores and metrics are back within your thresholds. Six months of trend history shows whether the fix stuck, and the monthly client reports put the latest Lighthouse results in front of your clients. The CLS lab default is 0.25.
Signal that proves it worked: CLS in the Lighthouse report under your threshold (0.1 if you want "good", not just "not poor"), and no new Lighthouse alert after the next deploy.
Make the page eligible for bfcache #
A page that restores instantly from the back-forward cache skips the load entirely: when a visitor presses back, the browser restores the frozen page instead of fetching, parsing, and rendering it again. Those instant back navigations count in your field data too.
You don't add bfcache, the browser does. But a page can make itself ineligible, usually through an unload event handler, a Cache-Control: no-store header, or a connection that's still open when the visitor leaves. Chrome DevTools has a test for it under Application, then Back/forward cache, which tells you exactly what's blocking it.
The checklist: 10 checks before you call it fixed #
Work the list in order. Stop at the first check that fails, fix it, and run the Core Web Vitals test again.
- Name the failing metric, with its threshold.
- Confirm lab or field.
- For LCP, find the largest of the four timings.
- Preload the hero asset; lazy-load below the fold.
- Fix the response phase over budget.
- For INP, find the slow interaction in field data.
- Break up long tasks; audit third-party scripts.
- For CLS, reserve space; animate transforms only.
- Check bfcache eligibility.
- Run the Core Web Vitals test twice and watch the signal for one deploy cycle.
You can catch the next regression before the client does if the test is scheduled and someone watches the same metric after every deploy. For agencies watching a portfolio of client sites, the fix is proven only when the next report still shows it.
Frequently asked questions #
How long until a Core Web Vitals fix shows in the field number?
The lab signal moves immediately. The field data in Search Console and PageSpeed Insights covers a rolling 28-day window, so a fix takes up to four weeks to show in full.
Why did my Lighthouse score improve but Core Web Vitals still fail?
A Lighthouse score is lab data; Core Web Vitals pass or fail on field data. The lab run can improve while real visitors still see a slow page, especially for INP, which the lab can only proxy with Total Blocking Time.
How do I improve Core Web Vitals without a developer?
Start with the server-phase and image steps: fix the response phase over budget, give images dimensions, and lazy-load below-the-fold content. Those cover most LCP and CLS failures. The main-thread steps for INP usually need a developer.
Do Core Web Vitals affect rankings?
Yes, but only a little. Google says its ranking systems use Core Web Vitals, as one signal among many. Passing them helps; failing them doesn't single-handedly decide where a page ranks, and relevant content still matters more. Treat the fix as a user-experience job first.