Website speed is a business metric
Core Web Vitals for the people who own the numbers: what LCP, INP and CLS measure, how Google scores them on real visits, and what we change to make each one good.
Slow pages cost money
In 2021, Vodafone ran the kind of test most arguments about speed are missing. It built two versions of one landing page, split its paid traffic evenly between them, and made only one of them faster. On the fast version, the main content appeared 31% sooner. That version made 8% more sales.1
A clean A/B test like that is rare, so it helps to know what the rest of the evidence looks like. In a study published in 2020, Deloitte Digital and Fifty-Five, working with Google, followed 37 brands for four weeks. When mobile pages got 0.1 seconds faster, retail sites saw 8.4% more conversions and a 9.2% higher average order value, and travel sites saw 10.1% more conversions.2 It is a correlation study: the speed changes happened naturally and were matched against each brand's analytics. And not every result pointed the same way. Lead generation sites saw lower bounce rates, but their conversion rates fell.
Google's own research points the same way. In work published in 2018, Google and SOASTA trained a model on bounce and conversion data. For mobile visitors, compared with a page that loads in one second, a page that takes three seconds raised the probability of a bounce by 32%. At five seconds the increase was 90%.3
The pattern holds when teams fix one thing at a time. Yahoo! JAPAN News stopped its article pages from jumping around and got 15% more page views per session.4 redBus, the Indian bus ticketing site, made its pages respond faster to taps and saw sales rise about 7%.5 We come back to both below.
So we treat speed the way a business treats ad spend or conversion rate: a number with an owner, a target and a monthly review. It is just as measurable. The rest of this post covers what to measure, how Google measures it, and what we change in a codebase to move each number.
Three numbers, scored by real visitors
Google's Core Web Vitals boil page experience down to three metrics.6 Each one answers a question a visitor would ask.
- Largest Contentful Paint (LCP): did the main thing show up? The time until the largest image or block of text on screen is drawn. Good is 2.5 seconds or less; poor is over 4 seconds.7
- Interaction to Next Paint (INP): when I tap, does it respond? The time from a click, tap or key press until the browser draws the next frame. Good is 200 milliseconds or less; poor is over 500.8
- Cumulative Layout Shift (CLS): does the page stay still? A score for how much visible content moves without warning. Good is 0.1 or less; poor is over 0.25.9
INP is the newest. It replaced First Input Delay on 12 March 2024. FID only measured the delay before the first interaction on a page; INP looks at every interaction in the visit.10,8
What matters most is how these numbers are scored. Google doesn't use the result of a test you run on your laptop. It uses field data: measurements from real Chrome users who have opted in to sharing usage statistics, published as the Chrome UX Report.11 PageSpeed Insights shows that data for the previous 28 days, and a page passes only when all three metrics are good at the 75th percentile.12 Mobile and desktop are scored separately.
The 75th percentile works like this. Line up a page's visits from fastest to slowest and take the one three-quarters of the way along. If that visit was good, at least three in four visits were good. Google picked this point so that most visits meet the target, while a few very slow outliers can't swing the result.7
This is how a green Lighthouse score ends up next to a failing report in Search Console. A lab test loads the page once, on one simulated device and network, usually with an empty cache, and nobody scrolls or taps.13 Real visitors are on mid-range phones and patchy mobile data, scrolling and tapping while the page is still loading. Lab tools are good for debugging and for catching regressions before a release. Field data tells you what to fix. A lab test that never interacts with the page can't measure INP at all; Total Blocking Time is the usual stand-in.8
Across the web, LCP is the metric that fails most often. In the 2025 Web Almanac, 62% of mobile websites had a good LCP, against 77% for INP and 81% for CLS, and only 48% passed all three.14 Google says good Core Web Vitals align with what its ranking systems seek to reward, but it doesn't say how much weight they carry.15 We treat search as a bonus. The real return is in conversion.
LCP: show the main thing sooner
On most pages the largest element is an image: on 76% of mobile pages in the 2025 Web Almanac, usually a hero banner or a product photo.14 web.dev splits LCP into four parts, and it pays to look at them in order:16
- Time to first byte: waiting for the server to send the HTML.
- Load delay: the gap between that first byte and the browser starting to fetch the image.
- Load duration: downloading the image itself.
- Render delay: the gap between the image arriving and it appearing on screen.
Most teams assume the problem is a heavy image. More often it is the second part: the browser finds the image late. If the hero is added by JavaScript, set as a CSS background or lazy-loaded, the browser can't start downloading it until long after it could have. web.dev notes that load duration is usually not the main bottleneck, and the Web Almanac finds that load delay often takes up a large share of LCP.16,14
Here is what we change, part by part.
Server time. Pages that look the same for every visitor, like home, category, product and blog pages, should be built ahead of time or cached, and served from a CDN close to the visitor. web.dev suggests a time to first byte of 0.8 seconds or less as a rough guide.17 A static page from a nearby cache skips most of that wait. A page that a CMS assembles on every request often doesn't.
Discovery. Put the hero in the HTML as a real <img>, not behind JavaScript. Give it fetchpriority="high" so the browser fetches it before other images, and never lazy-load it.16 That last mistake is common: about one in six mobile pages lazy-loads its LCP image.14 Chrome, Safari and Firefox all support fetchpriority now.18
Size. Serve AVIF or WebP, which compress better than JPEG and PNG, with a fallback where needed.19 Use srcset and sizes so a phone downloads a phone-sized file, not the 2,400-pixel desktop version. Most sites haven't switched yet: 57% of LCP images are still JPEGs.14
Fonts. Text can be the LCP element too, and text waits for its font. Serve fonts as WOFF2, subset them to the characters you use, and preload the one or two that appear on the first screen.20
Render delay. Large stylesheets and scripts in the <head> hold back the first paint, even after the image has arrived. Inline the little CSS the first screen needs, load the rest so it doesn't block, and give scripts defer.16
<!-- The hero: in the HTML, sized, fetched first -->
<picture>
<source type="image/avif"
srcset="shoe-800.avif 800w, shoe-1600.avif 1600w"
sizes="(min-width: 768px) 50vw, 100vw">
<img src="shoe-800.jpg"
srcset="shoe-800.jpg 800w, shoe-1600.jpg 1600w"
sizes="(min-width: 768px) 50vw, 100vw"
width="1600" height="1200"
fetchpriority="high" alt="Trail Runner 2">
</picture>
<!-- Below the fold: the browser can wait -->
<img src="review-1.webp" width="800" height="600"
loading="lazy" alt="A customer's photo of the shoe">Vodafone's faster page used several of these at once. It moved a widget from client-side to server-side rendering, rendered the critical HTML on the server and resized its hero image.1
INP: answer every tap
A browser has one main thread for running JavaScript, working out layout and painting the screen. While it's busy with a long task, which web.dev defines as anything over 50 milliseconds, it can't respond to input.21 The tap waits in a queue. The visitor sees a button that doesn't react, taps again, and either adds two items to the cart or gives up.
INP measures that whole wait in three parts: input delay (waiting for the thread to come free), processing (running your event handlers) and presentation delay (drawing the next frame).22 A page's INP is roughly its slowest interaction; on busy pages, one outlier is ignored for every 50 interactions.8
What we change:
- Ship less JavaScript. Every script has to be downloaded, parsed and run, often on a phone far slower than the laptop it was written on. We render the static parts of a page as plain HTML and send JavaScript only for the parts that need it.
- Break up long tasks. If you must process a long list or build a big section, do it in chunks and yield to the main thread between them, so a waiting tap can run.21
scheduler.yield()does this and resumes your work before other waiting tasks of the same kind. Chrome, Edge and Firefox support it, and asetTimeoutfallback covers Safari.21,23 - Paint first, then do the rest. In a click handler, update what the visitor sees, like the button and the cart count, and push saving, analytics and recalculation until after the next frame.22
- Question third-party scripts. Chat widgets, heatmaps and tag managers mostly run on the same main thread as your checkout button.
- Keep the page small. A huge DOM takes longer to lay out again after every interaction.22
// Let the browser handle a waiting tap between chunks.
function yieldToMain() {
if (globalThis.scheduler?.yield) return scheduler.yield();
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function renderReviews(reviews) {
let start = performance.now();
for (const review of reviews) {
renderReview(review);
// Yield roughly every 50 ms, not after every item.
if (performance.now() - start > 50) {
await yieldToMain();
start = performance.now();
}
}
}redBus traced its slow interactions to a scroll handler and to form fields that re-rendered too often. One fix, fetching 10 results at a time instead of 30, took that interaction from 870–900 ms down to 350–370 ms. Together the changes lifted sales by about 7%.5
CLS: keep the page still
Layout shifts are the most irritating speed problem and usually the cheapest to fix. Something loads late, pushes the content down, and the visitor loses their place or taps the wrong thing.
Browsers score each shift by multiplying two fractions: how much of the screen the moving content covers, before and after the move (impact fraction), and how far it moved compared with the screen's longest side (distance fraction). Shifts that come in a burst, less than a second apart and within five seconds in total, add up, and CLS is the worst burst. Shifts within half a second of a tap or key press don't count, because the visitor expected something to change.9
The common causes are few: images and videos without dimensions, ads and embeds without reserved space, content injected above what's already there, and web fonts that render bigger or smaller than their fallback.24
- Give every image
widthandheightattributes, or a CSSaspect-ratio. The browser then reserves the right space before the file arrives.24 In 2025, 62% of mobile pages still had at least one image without dimensions.14 - Reserve space for anything that arrives late: ad slots, embeds, promo bars, cookie notices. A
min-heighton the container is often enough.24 Better still, show banners as an overlay that doesn't push the page. - Stop fonts from reflowing text. Use
font-display: optional, which skips the web font if it arrives late, or tune the fallback font withsize-adjustso both take up the same space.24,20 - Animate with
transform, nottoporheight, so animations don't move anything else.24 - Keep pages eligible for the back/forward cache, so a visitor who presses Back gets the page instantly, exactly as they left it.24
/* Hold the slot open before the banner arrives */
.promo-slot { min-height: 72px; }
/* Embeds that have no width and height attributes */
.video { width: 100%; aspect-ratio: 16 / 9; }
/* Skip the web font if it is late: no swap, no shift */
@font-face {
font-family: "Brand";
src: url("/fonts/brand.woff2") format("woff2");
font-display: optional;
}Yahoo! JAPAN News reserved space for its images before they loaded. CLS in its lab tests went from about 0.2 to 0, page views per session rose 15% and sessions got 13% longer.4
Make speed someone's job
Websites get slower over time. A new tracking pixel here, a bigger hero video there, a chat widget for the sales team. None of them is a bad decision on its own, which is why speed needs an owner and a budget like any other business number. This is the routine we recommend:
- Watch field data by page template. Search Console's Core Web Vitals report and PageSpeed Insights show Chrome UX Report data for free. For your own numbers by page type, device and country, add real-user monitoring; Google's open-source
web-vitalslibrary is a good start.6 - Read the mobile 75th percentile first. It is the number Google scores, and usually your worst.
- Fix where the money is. A slow product page that gets 40% of visits matters more than a slow About page.
- Guard in the lab. Run Lighthouse on every release, with budgets for JavaScript size and load time, so regressions are caught before customers meet them.
- Price every new script. Before adding a tag or widget, ask what it costs in milliseconds as well as in rupees.
None of this needs a rebuild. Most fixes in this post are a few lines of HTML or CSS, a change to how a script loads, or a cache setting. The hard part is noticing, which is why the report matters more than any single fix.
Visitors don't notice a tenth of a second. Your sales report does.