Fast on your device, slow on your customer's phone: measure with real-user numbers before you fix anything
A business owner opens their website on a new phone over office Wi-Fi and sees the page in a second and a half. A customer on a mid-range phone over 4G can wait twice as long. Google judges speed by the 75th percentile of real users, not by what the site owner sees. This article explains the three Core Web Vitals in plain language, why a Lighthouse score is not one of them, three calculations you can do yourself, and six steps to measure, fix and measure again.
A shop owner opens their website on a newly bought phone over the office Wi-Fi. The page appears in about a second and a half. They conclude the site is fast, and they are completely right — for that phone, on that network.
At the same moment, a customer in a rural district opens the same page on a three-year-old mid-range Android phone over 4G with a weak signal. The hero image appears after almost four seconds. Just as it appears, the text below gets pushed down, and the thumb that was about to tap "Call now" lands on a different link.
The two people are looking at two different websites, even though the address is the same. And when Google judges page speed, it looks much more like the second person than the first.
The three numbers Google uses, in plain language
Core Web Vitals are the three metrics Google uses to measure the page-loading experience from the user's side. Each has two thresholds — below the first is good, above the second is poor:
- LCP (Largest Contentful Paint — when the largest piece of content appears, usually the hero image or headline block). Good: 2.5 seconds or less. Poor: over 4 seconds.
- INP (Interaction to Next Paint — the delay from a tap or click to the screen responding). Good: 200 milliseconds or less. Poor: over 500 milliseconds. INP replaced the older FID metric on 12 March 2024, so any guide still talking about FID is out of date.
- CLS (Cumulative Layout Shift — how much content jumps around while loading). Good: 0.1 or less. Poor: over 0.25.
The most important detail is how the number is taken: Google does not use an average but the 75th percentile (p75) — the value that 75% of visits match or beat. A page only passes when all three metrics are within the good threshold at p75. In other words, the slowest quarter of visits may be worse, but the rest must be good.
Why "it's fast on my device" is not an answer
To show how far p75 can sit from the owner's impression, we built an illustrative example. These are assumptions to show the mechanism, not measurements of any particular website: 60% of visitors have capable phones and good networks, with a typical LCP of 1.6 seconds; 40% have mid-range phones or weak networks, with a typical LCP of 3.6 seconds. Each group varies naturally around its typical value. Simulating one million visits gives:
- A typical visit in the capable group: 1.6 seconds — the number the owner sees.
- Mean across all visits: 2.6 seconds. Median: 2.05 seconds — by the median, the site looks like it passes.
- p75: 3.2 seconds — fails, though it has not reached the poor threshold.
- 38% of visits wait more than 2.5 seconds. In the weaker-device group, that share is 79%.
The site in the example is not slow for the people who built it. It is slow for nearly four in ten customers. That is exactly the group the p75 number is designed not to ignore.
The practical consequence: checking speed by opening the page yourself, even on a phone, almost always gives a more optimistic result than reality. The builder's device is usually faster, their network better, and their browser has already cached parts of the page from earlier visits.
Two kinds of numbers: real users and simulation
When you paste a page address into PageSpeed Insights, you get two very different sections on the same screen, and confusing them is the most common reason optimisation work heads in the wrong direction.
The top section is real-user data (field data). It comes from the Chrome UX Report (CrUX) — a report Google compiles from a portion of real Chrome users — over a rolling 28-day window, at p75, separated into phones and desktops. This is the number Google uses when it considers page experience.
The section below is simulated data (lab data) from a single Lighthouse run on Google's servers. On the mobile tab, Lighthouse simulates a network with 150 milliseconds of latency, 1.6 Mbps download speed, and a CPU slowed down 4 times to resemble a mid-range phone. The 0–100 score is a summary of that one run.
The 0–100 Lighthouse score is not Core Web Vitals. A page can score 95 and still fail for real users, or score 60 and still pass — because real users are not using that one simulated device.
The two kinds of numbers do different jobs. Real-user numbers answer whether there is a problem. Simulated numbers answer where the problem is, because they show each step of the page load. The right order is to read the real-user numbers first, then use the simulation to find the cause.
A note for small businesses: CrUX only publishes numbers for pages or sites with enough visits in the past 28 days, and Google does not publish the exact threshold. Many small and medium business websites will see "insufficient data". That does not mean there is no problem — only that Google is not measuring it for you. Step 5 below shows how to measure it yourself.
Calculation one: how many seconds the hero image costs
For most business websites, the element that determines LCP on phones is the hero image at the top of the page. And the most common culprit is an image that is far too heavy, usually a photo uploaded straight from a camera or a design export that was never compressed.
The calculation is simple: kilobytes × 8 ÷ network speed (kilobits per second) = minimum seconds to download the image. On the 1.6 Mbps network Lighthouse simulates:
- 100 KB: 0.5 seconds
- 200 KB: 1 second
- 500 KB: 2.5 seconds — the entire LCP budget spent just downloading the image
- 1.5 MB: 7.5 seconds
- 3 MB: 15 seconds
These are minimums, before connecting to the server, downloading the HTML, CSS and fonts, and the browser painting the image. A 3 MB hero image — not unusual when photos go straight from the camera — almost certainly puts the page in the poor zone on phones, however fast the server is.
Checking an image's size needs no special tool. Right-click the image, choose "Copy image address", then run in a terminal:
curl -sI <image-address> | grep -i content-length
The number returned is in bytes. Divide by 1,000 for kilobytes. If the line is missing (some servers compress in transit), open DevTools, go to the Network tab, filter by Img and read the Size column.
Calculation two: how one image without dimensions makes CLS fail
CLS scores each layout jump as the product of two parts: the share of the screen affected (impact fraction) times the distance the content moved (distance fraction), both measured as a share of the screen height.
Example: a block of text takes up 60% of the screen. Above it, an image with no declared height finishes loading and pushes the text down by 25% of the screen. The affected area is the old position plus the extension: 60% + 25% = 85%. The distance moved: 25%. The score for this one jump: 0.85 × 0.25 ≈ 0.21 — twice the 0.1 good threshold, from a single image.
The cheapest fix in this whole article: add width and height attributes to every img tag. The browser uses their ratio to reserve the space before the image arrives, even when CSS scales the image to fit the screen. Other common CLS culprits: cookie or promotion bars inserted at the top after the page has appeared, ad slots or embedded videos with no reserved space, and web fonts replacing a fallback font of noticeably different size.
Calculation three: INP and the code that isn't yours
INP measures the delay of (roughly) the slowest interaction during the whole time a user is on the page. The browser has a single main thread to both run JavaScript and paint the screen. When code runs without a break — what the browser calls a long task if it exceeds 50 milliseconds — the user's tap has to wait in line.
The calculation: a mid-range phone's CPU is about 4 times slower than a desktop's (the same factor Lighthouse uses to simulate one). Code that runs for 60 milliseconds on a developer's computer — which nobody notices — can become about 240 milliseconds on a customer's phone, already over the 200-millisecond threshold before counting the time to repaint the screen.
On business websites, most of the code running on a page is usually not written by the website team: live-chat widgets, ad-tracking tags from several platforms, embedded maps, session-recording tools, third-party review widgets. Each one is small when added, and rarely does anyone count them again.
What to do: open DevTools, go to the Network tab, filter by JS and sort by domain. List every domain that isn't yours and ask of each one: is anyone still using its data? The tracking tag of an ad campaign that ended last year is a free long task on every visit. Of the ones worth keeping, most can be loaded after the page has appeared — for example, a chat widget that only loads when the user taps the chat button.
Six steps: measure, fix, measure again
The order below is arranged so that each step uses the result of the one before, and so that cheap work comes before expensive work.
Step 1. Read the real-user numbers, mobile tab. Open pagespeed.web.dev, paste the address, choose Mobile, and read the top section before the score. Record the p75 for LCP, INP and CLS. If the page has insufficient data, look at the whole site. If the site has none either, do step 5 first.
Step 2. Find the LCP element. In Chrome, open DevTools (F12) and go to the Performance tab. Under the gear icon, set CPU to "4x slowdown" and the network to "Slow 4G", then click reload-and-record. The timeline has an LCP marker; click it to see which element it is. All LCP fixes start from that element.
Step 3. Measure that image's size. Use the curl -sI command above. A hero image for phones should be under roughly 200 KB; most photos reach that when exported as WebP or AVIF at a phone-screen width, with the srcset attribute giving desktops a larger version.
Step 4. Check the hero image is not deferred. In the Elements tab, find the hero's img tag. If it has loading="lazy", remove it — that attribute tells the browser to load the image late, which is right for images further down the page but wrong for the one at the top. Add fetchpriority="high" so the browser prioritises it. If the hero is a JavaScript slider, the image only starts loading after the slider code has run — consider making the first image a plain img tag in the HTML. At the same time, check that every img and iframe has width and height.
Step 5. Collect your own real-user numbers. Google publishes the open-source web-vitals library to measure exactly these three metrics in customers' browsers. The minimal code:
import {onCLS, onINP, onLCP} from 'web-vitals';
const send = (m) => navigator.sendBeacon('/analytics', JSON.stringify({name: m.name, value: m.value, rating: m.rating, id: m.id}));
onCLS(send); onINP(send); onLCP(send);
Replace /analytics with your own collection endpoint — or send the values as events into the analytics tool you already use. navigator.sendBeacon can send even while the customer is closing the page, which matters because INP and CLS are only finalised when the user leaves. After a few weeks, compute p75 for each metric, separating phones from desktops. The library includes a rating field (good / needs-improvement / poor) using exactly the thresholds above, so there is no need to compare against thresholds yourself.
Step 6. Fix one thing, record before and after, wait long enough. Fix one cause at a time so you know which one worked. PageSpeed Insights shows a rolling 28-day window, so after a fix the number shifts gradually over about four weeks rather than changing at once. Your own numbers from step 5 show the result within days. This is why speed optimisation is a loop, not a one-off fix.
Back to the illustrative example: if compressing the hero image and removing lazy-loading makes the capable group 0.3 seconds faster and the weaker group about 1.3 seconds faster (the weaker group gains more because the network is their bottleneck), p75 drops from 3.2 to 2.2 seconds — a pass, and the share of visits waiting more than 2.5 seconds falls from 38% to 19%. No new server, no rewrite of the website.
What about search rankings?
Google confirms that Core Web Vitals are part of the page-experience signals its ranking systems use. But Google is also clear that content relevant to the searcher's question is still preferred, even when the page experience is not good. A fast page that does not answer the question will not climb on speed alone.
So the main reason to fix speed is not ranking. It is the customer from the start of this article: someone who clicked your link — meaning you already paid for that click, in advertising or in the effort of making the content — then had to wait four seconds, and tapped the wrong thing because the button moved.
Conclusion
"Is the website fast?" has no single answer, because whether a website is fast depends on who is opening it. The question that does have an answer is: at the 75th percentile of real customers, on phones, what are the three numbers?
Most business websites fail that question for a handful of familiar reasons — one image too heavy, one image deferred in the wrong place, a few images without dimensions, a few third-party scripts nobody uses any more. All of these can be fixed in a few days. What is usually missing is the habit of measuring before fixing and measuring again afterwards.
If you want someone to measure your website with real-user numbers, find the actual causes and fix them in a measure–fix–measure loop, contact us or see KonexForge's Optimization Loop capability.
Related articles
Before running an A/B test, work out one number — for most Vietnamese sites it exceeds a year of traffic
You do not have to run an A/B test to find out whether it can conclude anything. One calculation, about a minute's work, gives the minimum number of visitors required. For a site with 500 visitors a day, detecting a 10% improvement needs 322 days — and the habit of checking results daily pushes the false-win rate from 4% to 20%.
Designing an alert pipeline that avoids alert fatigue: from static thresholds to anomaly detection
An alert system with thresholds set too sensitively will send dozens of notifications a day — the team gradually ignores them, and real alerts get buried in the noise. Thresholds set too high miss incidents until it's too late. A three-layer alert pipeline — static threshold, dynamic baseline, and anomaly detection — combined with routing and escalation policies to deliver the right alert to the right person at the right time.
How production AI models degrade — and how to detect it before it's too late
After deployment, a model keeps receiving inputs and returning predictions — but the world around it changes. Data drift and concept drift are the two mechanisms by which a once-accurate model gradually loses reliability without producing a single log error. How to detect degradation early, design a three-layer monitoring pipeline, and decide when to retrain versus fix the root cause.