Websites

Website speed is a business metric

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

Increase in the chance a mobile visitor bouncescompared with a page that loads in 1 second
Page load timeGoogle/SOASTA Research, 2017
How the chance of a bounce grows with load time on mobile, compared with a page that loads in one second. Google published these four points; the line only joins them. Source: Google/SOASTA Research, 2017 [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

one visit
Lab test, 1 run–
Field data, p75–
One page, 100 visits (illustrative data). Each dot is a visit; the blue one is the 75th, the visit Google scores. The dashed line is a single lab test. Switch metrics to see why the two rarely agree.

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

  1. Time to first byte: waiting for the server to send the HTML.
  2. Load delay: the gap between that first byte and the browser starting to fetch the image.
  3. Load duration: downloading the image itself.
  4. 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

LCP…
The same product page before and after the fixes below (illustrative timings on a phone). The playhead runs through the network requests while the browser shows what the visitor sees. Blue is the hero image, the LCP element.

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

product.html
<!-- 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">
A hero the browser can find, size and fetch first straight from the HTML, and a below-the-fold image it can safely leave for later.

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

‹ Shop
Trail Runner 2₹4,999Loading reviews…
INP…Input delay –Processing –Presentation –
Tap “Add to cart” yourself, while reviews load and after. Played 6× slower.
The same 480 ms of work, as one long task or as ten short ones (illustrative). A tap during a long task waits for all of it. Tap the button yourself at different moments.

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.21scheduler.yield() does this and resumes your work before other waiting tasks of the same kind. Chrome, Edge and Firefox support it, and a setTimeout fallback 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
reviews.js
// 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();
    }
  }
}
Chunked work with a yield about every 50 ms. Where scheduler.yield() is missing, setTimeout still gives a tap its turn.

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

0.6 sPhoto arrives with no size set–
1.4 sApp banner lands above the button–
CLS0.00Good
A shift scores impact fraction × distance fraction. Shifts less than a second apart add up.
A product page with no reserved space (illustrative). The photo and a late app banner push the page down, and a tap meant for Add to cart opens the app store. Scores are measured from the mock page the way browsers measure them.

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 width and height attributes, 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-height on 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 with size-adjust so both take up the same space.24,20
  • Animate with transform, not top or height, 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
layout.css
/* 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;
}
Three small rules that prevent the most common layout shifts.

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:

  1. 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-vitals library is a good start.6
  2. Read the mobile 75th percentile first. It is the number Google scores, and usually your worst.
  3. Fix where the money is. A slow product page that gets 40% of visits matters more than a slow About page.
  4. 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.
  5. Price every new script. Before adding a tag or widget, ask what it costs in milliseconds as well as in rupees.
Speed reportMobile · p75 · last 28 days
TemplateVisitsLCPINPCLS
Productfix first41%3.8 s, Needs improvement180 ms, Good0.21, Needs improvement
Category22%2.9 s, Needs improvement230 ms, Needs improvement0.06, Good
Home18%2.2 s, Good150 ms, Good0.04, Good
Checkout10%2.1 s, Good140 ms, Good0.01, Good
Cart9%1.9 s, Good520 ms, Poor0.02, Good
A monthly speed report for a fictional shop (illustrative numbers). One row per page template, field data only, ordered by traffic. The product page gets the most visits and misses on two metrics, so it goes first.

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.

References
1web.dev (2021). Vodafone: A 31% improvement in LCP increased sales by 8%. Google. A/B test on a landing page, paid traffic split 50/50.
2Deloitte Digital and Fifty-Five, with Google (2020). Milliseconds Make Millions. Think with Google. 37 brands, about 30 million mobile sessions over four weeks; a regression study of natural speed changes.
3An, D. (2018). Find out how you stack up to new industry benchmarks for mobile page speed. Think with Google (data: Google/SOASTA Research, 2017). Archived copy.
4Kiraku, T., Mihajlija, M. and Shishido, S. (2021). How CLS optimizations increased Yahoo! JAPAN News's page views per session by 15%. web.dev.
6Walton, P. (2020). Web Vitals. web.dev, updated 2024.
7McQuade, B. and Pollard, B. (2020). How the Core Web Vitals metrics thresholds were defined. web.dev, updated 2025.
8Wagner, J. and Pollard, B. (2022). Interaction to Next Paint (INP). web.dev, updated 2025.
9Mihajlija, M. and Walton, P. (2023). Cumulative Layout Shift (CLS). web.dev.
10Wagner, J. and Viscomi, R. (2024). Interaction to Next Paint becomes a Core Web Vital on March 12. web.dev.
11Chrome for Developers (2024). CrUX methodology. Google.
12Google for Developers (2024). About PageSpeed Insights. Google.
14Jariyal, H., Rasam, P., Humaira and Grogg, A. T. (2026). Performance. The 2025 Web Almanac, HTTP Archive.
15Google Search Central (2025). Understanding Core Web Vitals and Google search results. Google.
16Walton, P. and Pollard, B. (2020). Optimize Largest Contentful Paint. web.dev, updated 2025.
17Pollard, B. and Wagner, J. (2021). Time to First Byte (TTFB). web.dev, updated 2025.
18MDN contributors (2026). <img>: The Image Embed element. MDN Web Docs. Browser support for fetchpriority, loading and width/height aspect ratios.
19MDN contributors (2026). Image file type and format guide. MDN Web Docs.
20Hempenius, K. and Pollard, B. (2022). Best practices for fonts. web.dev.
21Wagner, J. and Kenny, B. (2022). Optimize long tasks. web.dev, updated 2024.
22Wagner, J., Walton, P. and Pollard, B. (2023). Optimize Interaction to Next Paint. web.dev, updated 2025.
23MDN contributors (2025). Scheduler: yield() method. MDN Web Docs.
24Osmani, A. and Pollard, B. (2020). Optimize Cumulative Layout Shift. web.dev, updated 2025.

I want to a.

Tell us where it is today, what you’re trying to achieve and what’s getting in the way.