Guide · Frontend
Core Web Vitals Explained: LCP, INP and CLS With Real Fixes
28 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- Core Web Vitals are three measurements of how a page feels to real visitors. Largest Contentful Paint (LCP) covers loading, Interaction to Next Paint (INP) covers response to taps, and Cumulative Layout Shift (CLS) covers visual stability.
- Good means LCP of 2.5 seconds or less, INP of 200 milliseconds or less and CLS of 0.1 or less. You check each at the 75th percentile (p75): the value that 75 out of 100 visits are at or below.
- LCP often suffers because the browser finds the main image late. Put it in the HTML, never lazy-load it (lazy loading tells the browser to wait before fetching), and give it high fetch priority.
- INP improves when a tap handler paints a small change first, then yields (pauses so the browser can draw), and does the slow work later. CLS drops when you reserve space for anything that loads late.
- Beginners can stop after the section on checking your own page. Engineers get a common layout shift mistake fixed, key fixes measured in Chromium 141 (the open-source core of Chrome), and which browsers report each metric as of 19 October 2026.
What goes wrong when a page feels fast to you but slow to shoppers?
Shoppers wait for the main photo, tap and see nothing happen, and watch the page jump under their thumb. You miss it because you test on a strong laptop with fast Wi-Fi, while they use budget phones on patchy mobile data.
Core Web Vitals turn that gap into three numbers you can measure and fix.
Picture a common setup. An online jewellery store sells silver anklets and bridal sets to shoppers in smaller towns, mostly on phones that cost under Rs 12,000. The setup is made up, but the problems are real.
The team ships a new product page before the wedding season. On a developer laptop it looks lovely. A big ring photo fills the top of the screen, and a size picker sits just below it.
Then the team looks at numbers collected from real shoppers' phones. Three complaints from the support chats each match one of those numbers.
- The ring photo takes almost five seconds to show up. Many shoppers leave before they see the product.
- Tapping a ring size seems to do nothing for half a second. Some shoppers tap twice and pick the wrong size.
- A free delivery banner appears late and pushes everything down. Shoppers aiming for the size picker tap the wishlist button instead.
These three problems are exactly what Core Web Vitals measure. The rest of this guide measures each one on the same product page, finds the cause and fixes it.
What are Core Web Vitals?
Core Web Vitals are three measurements of real user experience on a web page: Largest Contentful Paint (LCP), Interaction to Next Paint (INP) and Cumulative Layout Shift (CLS). Each one answers a question a shopper feels without naming it.
- LCP asks: how soon did the main content appear? It is the time until the biggest image or text block in view is painted on screen.
- INP asks: when I tapped, how soon did the page react? It is close to the slowest delay between a tap and the next screen update in the visit.
- CLS asks: did things jump around while I was reading? It scores how much visible content moved without the shopper asking for it.
Think of a jaadugar, a street magician, doing his show at a village mela (fair). Map the show onto the three metrics like this.
- The crowd does not care when he unrolled his mat. It cares when the main trick appears. That moment is LCP.
- A volunteer hands him a coin and the crowd watches how fast he reacts. A long pause feels like the trick failed. That pause is INP.
- If his cups and scarves keep sliding across the table, nobody can follow the trick. That sliding is CLS.
Where the picture breaks: a crowd judges one show. Core Web Vitals judge thousands of visits together, so one lucky visit on a fast phone proves nothing.
That is why each metric is checked at the 75th percentile of visits, split by mobile and desktop. The 75th percentile (p75) is the value that 75 out of 100 visits are at or below. An average would hide the slow quarter of your visitors.
The set can change, but slowly. The web.dev Web Vitals page says stable Core Web Vitals will not change more than once per year. INP replaced First Input Delay (FID) on 12 March 2024.
FID measured only the wait before the first interaction's code started. INP looks at every tap, click and key press in the visit, until the next frame is painted.
What is Largest Contentful Paint and how do you fix a slow LCP?
Largest Contentful Paint is the time from when the page starts loading until its largest visible image or text block is painted. A good LCP is 2.5 seconds or less, and anything over 4 seconds is poor.
The browser keeps a running guess while the page loads. Each time a bigger element paints, it becomes the new LCP candidate. The browser stops reporting new candidates once the visitor taps, scrolls or presses a key.
Candidates are images, images inside an SVG drawing, videos, elements with a background image loaded through url(), and blocks of text. Only the part inside the viewport counts. The viewport is the visible area of the screen without scrolling. On the product page, the ring photo is the LCP element.
Break LCP into four parts
Do not try to fix LCP as one big number. Split it into four parts, so you can see which one is slow. Follow the chain below from opening the page to the painted ring photo.
Where the product page's 4.6 seconds of LCP went
Drag to exploreThe four parts add up to the total LCP. Notice that the load delay, which should be tiny, is the second-biggest part. Timings are p75 on mobile (made up).
Diagram connections:
- Shopper opens product page to Time to first byte
- Time to first byte to Resource load delay
- Resource load delay to Resource load duration
- Resource load duration to Element render delay
- Element render delay to Ring photo painted
- Shopper opens product page: The clock starts at 0 seconds
- Time to first byte: First bytes of HTML arrive: 0.9 s
- Resource load delay: Carousel script finds the photo late: 1.4 s
- Resource load duration: A 1.8 MB photo downloads: 1.7 s
- Element render delay: Waits for fonts and CSS: 0.6 s
- Ring photo painted: LCP at 4.6 s, well past the 2.5 s line
Time to first byte (TTFB) is the time from opening the page until the first byte of HTML arrives. It includes the network trip and connection setup as well as the server's own work. Resource load delay is the gap between that and the moment the browser starts fetching the photo. Resource load duration is the download itself. Element render delay is the wait after the download, before the photo is painted.
The web.dev LCP guide gives a rough target: about 40% each for TTFB and the download, and under 10% each for the two delays. It calls these guidelines, not strict rules. On the product page, the load delay alone took 1.4 seconds.
The fixes that moved the number
The cause of the long delay was simple. The ring photo sat inside a carousel that JavaScript built after the page loaded. Worse, the carousel marked every slide, including the first, as lazy-loaded. Lazy loading tells the browser to wait before fetching an image.
- Put the first photo in the HTML as a plain img tag with a src, so the browser finds it while reading the page.
- Remove lazy loading from that one image. Keep it on photos further down the page.
- Set fetchpriority, an img attribute that hints how urgent a download is, to high on the first photo only and to low on hidden carousel slides.
- Serve a photo sized for the phone, in a modern compressed format, through a CDN close to the shopper.
- Cut render-blocking CSS and move synchronous scripts out of the head, so the painted photo is not held back.
Render-blocking means the browser will not paint the content after that file until the file has arrived and been processed. A synchronous script is one without async or defer, so the browser stops reading the HTML to run it. A CDN (content delivery network) is a set of servers in many cities that keep copies of your files near users.
Do not mark every image as high priority either. The web.dev LCP guide notes that setting high priority on more than one or two images makes the setting unhelpful for LCP. Image size and format choices have their own guide.
What is Interaction to Next Paint and how do you improve INP?
Interaction to Next Paint measures how long the page takes to show a visible response after a tap, click or key press. A good INP is 200 milliseconds or less, and over 500 milliseconds is poor.
INP watches every click, tap and key press during the visit, on a physical or on-screen keyboard. Hovering, zooming and scrolling are not counted. For most pages it reports the slowest interaction. On pages with many interactions, it ignores the single highest for every 50, so one freak tap does not decide the score.
The key word is paint. INP stops counting when the browser draws the next frame after your code runs. A network request that is still waiting does not count, as long as the screen updated.
Back at the mela, the jaadugar does not need to finish the whole trick at once. He only needs to close his fist and smile straight away, so the crowd knows the trick has begun.
The three parts of one interaction
Every interaction has three parts, and the diagram follows one tap on the size picker. Look at which box holds the most time, because that tells you where to look in the code.
One slow tap on the ring size picker
Drag to exploreINP is the whole span from the tap to the next painted frame. Here the handler's own work is the biggest part, so the fix belongs in the code. Timings are for a slow tap (made up).
Diagram connections:
- Shopper taps size 12 to Input delay
- Input delay to Processing duration
- Processing duration to Presentation delay
- Presentation delay to Next frame painted
- Shopper taps size 12: A budget phone on mobile data
- Input delay: Main thread busy with a reviews widget: 180 ms
- Processing duration: Click handler redraws the whole page: 300 ms
- Presentation delay: Browser lays out and paints: 60 ms
- Next frame painted: Size 12 looks selected after 540 ms
To read this, you need one more term. The main thread is the single line of work where the browser runs your JavaScript, handles taps and prepares each frame for the screen. It can do only one of those things at a time.
A long task is any task that keeps the main thread busy for longer than 50 milliseconds. While a long task runs, a tap has to wait in line. That wait is the input delay.
On the product page, a third-party reviews widget ran a long task just as many shoppers tapped. Then the click handler did everything at once. It redrew the whole product page, recalculated the EMI (equated monthly instalment) options and sent an analytics event, all before the browser could paint.
Show feedback first, then yield
The fix is to split the handler. First do the small visual change the shopper needs to see. Then yield, which means pausing your code so the browser can paint and handle other taps. Do the rest afterwards.
Here is the new handler. The helpers showSelectedSize, updatePriceAndStock and trackEvent are stand-ins for the store's own code. The yieldToMain helper is real and small, and the sample type-checks with TypeScript 7.0.2.
type YieldingScheduler = { yield?: () => Promise<void> };
function yieldToMain(): Promise<void> {
const s = (globalThis as { scheduler?: YieldingScheduler }).scheduler;
if (s?.yield) return s.yield();
return new Promise((resolve) => setTimeout(resolve, 0));
}
const picker = document.querySelector<HTMLElement>("#size-picker")!;
picker.addEventListener("click", async (event) => {
const button = (event.target as HTMLElement).closest("button");
if (!button) return;
showSelectedSize(button); // tiny change, painted first
await yieldToMain(); // let the browser paint now
updatePriceAndStock(button.dataset.size);
await yieldToMain();
trackEvent("size_selected"); // analytics can wait
});The yieldToMain function uses scheduler.yield when the browser has it. As of 19 October 2026, MDN's compatibility data lists it in Chrome 129 and later and Firefox 142 and later, but not in Safari. There the code falls back to a zero-delay setTimeout, whose callback runs in a new task.
The two paths are not identical. With scheduler.yield, the rest of your handler runs before other queued tasks of the same kind. The setTimeout fallback gets no such priority, so other waiting tasks may run first.
Inside the handler, the order is the whole point. The shopper sees size 12 highlighted in the first frame. The price update and the analytics event run in later, separate tasks, so they no longer hold up that frame.
We ran this pattern in Chromium 141 with 250 ms of price work and 80 ms of analytics work. Done all at once, each tap measured 336 to 344 ms. With feedback first and two yields, each tap measured 16 to 24 ms. The slow work still ran; it just no longer blocked the first paint.
The team made three more changes. They loaded the reviews widget only when shoppers scroll near it. They stopped redrawing parts of the page that had not changed. They also cut the number of HTML elements in the product grid, since rendering work tends to grow with the number of elements.
What is Cumulative Layout Shift and why does the page jump?
Cumulative Layout Shift is a score for how much visible content moves unexpectedly while the page is open. A good CLS is 0.1 or less, and anything above 0.25 is poor. It has no unit, because it is a score, not a time.
The page jumps because something appears or resizes after the content around it has already been drawn. The browser then has to move everything below it. These are the jaadugar's props sliding across the table.
How one shift is scored
Each shift gets a score: impact fraction times distance fraction. The impact fraction is the share of the viewport that the moving content covers, before and after the move combined. The distance fraction is how far it moved, divided by the viewport's larger side, width or height.
Pause and predict. The size picker and buttons fill the lower half of a phone screen. The late banner pushes them down by a quarter of the screen height. What does this one shift score?
Before the move, the picker covers the lower half: 50% of the viewport. After the move, its bottom half sits below the screen, and only the part inside the viewport counts. So before and after together still cover half the screen, and the impact fraction is 0.5.
On a phone held upright, the height is the larger side, so the distance fraction is 0.25. The shift scores 0.5 times 0.25, which is 0.125. One banner alone takes the page past the good line of 0.1.
What just happened? It is tempting to say 0.75 times 0.25, about 0.19, by counting the area pushed off the screen. That only works when the moved content starts in the top half and stays on screen. Content below the viewport never counts.
We checked all three cases in Chromium 141 on a 400 by 800 pixel viewport. The lower half pushed down 200 pixels scored 0.125. A top-half block pushed down 200 pixels scored 0.1875. A banner at the very top that pushed the whole screen scored 0.25.
Shifts that come close together are grouped into a session window. A session window is a burst of shifts with less than one second between them, lasting at most five seconds. CLS is the score of the worst session window, not the total for the whole visit.
In our test, shifts of 0.05 and 0.0475 half a second apart, then one of 0.09 two seconds later, gave a CLS of 0.0975. The first burst won; the 0.09 shift sat alone in its own window.
Not every movement counts. Shifts within 500 milliseconds of a tap, click or key press are treated as expected, so they are left out. Scrolls, drags and pinch-zoom do not count as such input. Opening a menu when the shopper taps the menu button is fine.
The usual causes and their fixes
| Cause | What the shopper saw | Fix |
|---|---|---|
| Images with no size set | Text slid down as each photo arrived | Set width and height on every img, or reserve space with CSS aspect-ratio |
| Late free delivery banner | The size picker dropped a quarter of the screen | Reserve the banner's space in the first HTML, or show it as an overlay |
| Ad and video embed slots | Buttons moved when the embed resized itself | Give the slot a min-height for its likely size, and keep a placeholder if nothing loads |
| Web font swapping in | Product names grew and wrapped onto a new line | Match the fallback font's size with size-adjust, preload the main font, or use font-display optional |
| Animating top or left | The price box slid and pushed its neighbours | Animate with transform instead, which does not move other elements |
Reserving space is the idea behind most of these. If the browser knows how big a thing will be before it arrives, it leaves a gap of the right size. When the thing loads, it fills the gap and nothing else moves.
How can you see LCP, INP and CLS on your own page?
You can watch all three on your own page in about two minutes, using Chrome's developer tools. The snippet below prints each LCP candidate, each unexpected layout shift and each click or key press that takes 16 ms or more, as it happens.
Open your page in Chrome
Use a page you can reload. Turn on mobile emulation in DevTools if you want a phone-sized viewport.
Open the console
Press F12 (Cmd+Option+I on a Mac) or right-click and choose Inspect, then open the Console tab.
Paste the snippet and press Enter
It starts three performance observers. A performance observer is a browser feature that hands your code timing entries as they are recorded.
Use the page
Wait for it to finish loading, then tap a few buttons. Watch the lines that appear.
// Paste into the DevTools console on your page, then tap something and watch.
const log = (...a) => console.log(...a);
new PerformanceObserver((list) => {
for (const e of list.getEntries()) {
log("LCP candidate:", Math.round(e.startTime), "ms,", e.element?.tagName ?? "removed element");
}
}).observe({ type: "largest-contentful-paint", buffered: true });
new PerformanceObserver((list) => {
for (const e of list.getEntries()) {
if (!e.hadRecentInput) log("Layout shift:", e.value.toFixed(3), "(unitless score)");
}
}).observe({ type: "layout-shift", buffered: true });
new PerformanceObserver((list) => {
for (const e of list.getEntries()) {
if (e.name === "click" || e.name === "keydown") log("Interaction:", e.name, e.duration, "ms");
}
}).observe({ type: "event", durationThreshold: 16 });On a test page with a photo and a late banner, Chromium 141 printed lines like these. LCP candidate: 332 ms, IMG. Layout shift: 0.125 (unitless score). Interaction: click 128 ms, for a button whose handler did 120 ms of work.
Three details help you read the output. LCP and interaction times are in milliseconds, and layout shift scores have no unit. Each layout shift line is one shift, not your CLS, which groups shifts into session windows.
Interaction times come in steps of 8 ms, because the Event Timing standard rounds them to the nearest 8 ms. The snippet only logs interactions of 16 ms or more, the lowest threshold the Event Timing standard allows.
What is the difference between field data and lab data?
Field data comes from real visitors on their own phones and networks, while lab data comes from a test you run on one machine under set conditions. Core Web Vitals are judged on field data, and lab data helps you debug.
Lab data is repeatable, so it is good for catching a slow change before you release it. But it cannot copy every budget phone on a weak signal. Lighthouse, a common lab tool, cannot measure INP in its standard page-load report, because nobody taps during the test.
That report shows Total Blocking Time instead, as a stand-in for responsiveness. It adds up how long tasks ran past 50 ms during page load. In Lighthouse 13.5.0 the INP audit runs only in timespan mode, where you record yourself or a script using the page. Chrome DevTools can show all three metrics while you use the page yourself.
For field data, you have two choices. Public reports, such as the Chrome User Experience Report (CrUX) and the Core Web Vitals report in Search Console, show results from real Chrome users. Or you can collect your own, which is called real user monitoring (RUM).
As of 19 October 2026, the CrUX API documentation says its data is a 28-day rolling average, updated daily. The Search Console report also describes the last 28 days. A fix you ship today takes weeks to show fully in those reports.
The team wanted its own data, split by page and by phone type. The diagram shows how one number travels from a shopper's phone to a dashboard. Notice that the lab test feeds the same dashboard, so both views sit side by side.
How the store collects Core Web Vitals
Drag to exploreField data decides pass or fail. The lab test on each release catches problems before shoppers see them, and the alert names the page and metric to fix.
Diagram connections:
- Shoppers' phones to Small measuring script
- Small measuring script to Metrics collector (When the page is hidden)
- Metrics collector to Metrics store
- Metrics store to p75 dashboard
- Lab test on each release to p75 dashboard
- p75 dashboard to Any p75 over its line?
- Any p75 over its line? to Alert the on-call developer (Yes)
- Shoppers' phones: Real devices on real mobile networks
- Small measuring script: Watches LCP, INP and CLS in the page
- Metrics collector: Receives one small report per metric
- Metrics store: Keeps values by page, device and date
- p75 dashboard: Mobile and desktop shown separately
- Lab test on each release: Throttled phone profile, same pages each time
- Any p75 over its line?: 2.5 s, 200 ms and 0.1
- Alert the on-call developer: Names the page and the metric
The measuring script uses the browser's PerformanceObserver. Here is the LCP part. The sendToCollector helper is a stand-in; in a real page it would call navigator.sendBeacon, which can send data while the page unloads.
let lcp = 0;
let sent = false;
new PerformanceObserver((list) => {
const entries = list.getEntries();
lcp = entries[entries.length - 1].startTime; // latest candidate wins
}).observe({ type: "largest-contentful-paint", buffered: true });
document.addEventListener("visibilitychange", () => {
if (document.visibilityState !== "hidden" || sent || lcp === 0) return;
sent = true;
sendToCollector({
name: "LCP",
value: Math.round(lcp),
page: location.pathname,
});
});Two lines matter most. The buffered option asks for entries recorded before the script started, so an early paint is not missed. The last entry is used because each new, larger candidate replaces the one before.
The report goes out when the page becomes hidden, such as when the shopper switches apps, because browsers often fire no further callbacks once a page is in the background. In our Chromium 141 test, leaving the page sent one report with the same LCP the page had measured.
This sample skips real edge cases. Pages opened in a background tab should not report LCP, and pages restored from the back/forward cache (the instant restore when you press Back) need a fresh measurement. The open-source web-vitals library handles these, and the extra rules for INP and CLS.
One more gap matters for stores with embedded widgets. INP counts interactions inside iframes, but the browser API does not report them to your page's script. CrUX includes iframe content, so its numbers can differ from your own RUM.
What did testing these fixes in a real browser show?
Each check below behaved as this guide describes, and one of them corrects a common calculation. The table lists each check, run in headless Chromium 141.0.7390.37 (no window on screen) through Playwright, a browser automation tool, with web-vitals 6.2.3, in October 2026.
| What we checked | Setup | Result |
|---|---|---|
| Lazy-loading the LCP image | Photo served after 300 ms, stylesheet after 200 ms, eager vs lazy | Request started about 15 ms vs 220 ms in; LCP about 340 ms vs 550 ms |
| Image added by a script | Same photo inserted by inline code after a script file that took 400 ms to arrive | Request started about 425 ms in; LCP about 750 ms |
| LCP after a click | Bigger heading added 100 ms after a click | Not reported; the earlier paragraph stayed the LCP |
| Feedback first, then yield | 250 ms price work plus 80 ms analytics in one click | 336 to 344 ms per tap all at once; 16 to 24 ms with two yields |
| Waiting on the network | Click changes a label, then a 2 s fetch | Interaction 16 ms or less; the fetch did not count |
| Long task line | Busy tasks of 45 ms and 60 ms | Only the 60 ms task reported |
| Shift of the lower half | Lower half pushed down 200 pixels | 0.125, not 0.1875 |
| Reserving space | Late photo with and without width and height; fixed banner | Sized photo and fixed banner: no shift; unsized photo: a shift |
| Animating top vs transform | Price box moved 100 pixels over 0.5 s | top: 30 small shifts; transform: none |
| Input delay | Click during a 180 ms busy task | Handler started about 160 ms after the click |
| Shift soon after input | Shifts 100 ms and 700 ms after clicks | First flagged as recent input and left out; second counted |
Two limits apply. Headless Chromium on a laptop is not a budget phone, so read these numbers as direction, not field values. Chromium 141 is also older than the stable Chrome of October 2026 (Chrome 155), so recheck timings on a current build. We also ran nothing here in Firefox or Safari, and fixes such as fetchpriority, CDNs and font loading were sourced, not measured.
Which Core Web Vitals fix should you make first?
Fix the metric that is furthest into the poor range on your most visited pages, using field data on mobile. Then pick the single cause that takes the biggest share of that metric.
The team wrote the three numbers side by side, before and after. The table below is the summary it shared with the rest of the store.
| Metric | Good line | Before | Main cause | Main fix | After |
|---|---|---|---|---|---|
| LCP | 2.5 s or less | 4.6 s | Ring photo found late and too heavy | Plain img in HTML, high priority, smaller file | 2.2 s |
| INP | 200 ms or less | 540 ms | One long click handler and a reviews widget | Feedback first, yield, load the widget later | 170 ms |
| CLS | 0.1 or less | 0.28 | Late banner and photos with no size | Reserved space, width and height on images | 0.04 |
The order of work followed cost and impact. CLS was the cheapest fix, so it shipped in a day. LCP came next, because shoppers who never see the product never tap anything. INP took the longest, since it meant reworking the handler and the widget.
Field numbers move slowly after a fix, because the p75 shifts only as new visits replace older ones. In 28-day reports like CrUX, give each change about four weeks. Your own RUM data, read daily, shows the trend sooner.
Pick the pages
Start with the pages most shoppers land on: the home page, category pages and product pages. A fix there helps the most visits.
Find the worst metric
Check p75 on mobile for each page. Choose the metric furthest past its good line.
Split it into parts
Break LCP into its four parts, or INP into its three. For CLS, find which element moved and when.
Fix one cause at a time
Change one thing, release it, and watch the field data. Two changes at once hide which one helped.
Guard it with a budget
Add a lab check on each release and an alert on field p75, so the number does not slide back.
How do you explain Core Web Vitals in a system design interview?
Explain them as three user-facing goals, then show how your design meets each one. In a frontend system design round, they turn "make it fast" into numbers you can design for and check.
Name the three metrics
Loading is LCP, responsiveness is INP, and visual stability is CLS. Give the good lines: 2.5 seconds, 200 milliseconds and 0.1, at p75.
Identify the LCP element
Say which element will likely be largest, such as the product photo. Explain how the HTML, server rendering and CDN get it painted early.
Plan the main thread
List the heavy interactions, like filters or add to cart. Say how handlers give feedback first and push slow work into later tasks.
Reserve space everywhere
Mention sizes on images, fixed slots for banners and ads, and a font loading plan, so the layout never jumps.
Measure in the field
Describe real user monitoring by page and device, a p75 dashboard, alerts, and a lab check on every release. Note that CLS from your own script covers Chromium only.
Name the trade-offs
More server rendering helps LCP but costs server time. Deferring scripts helps INP but delays analytics. Say which you would choose and why.
Questions people ask
- What are the three Core Web Vitals?
- The three Core Web Vitals are Largest Contentful Paint, Interaction to Next Paint and Cumulative Layout Shift. LCP measures loading, INP measures how quickly the page responds to taps, clicks and key presses, and CLS measures how much the layout jumps. A page should be in the good range for all three at the 75th percentile of visits.
- What is a good Core Web Vitals score?
- A good score is an LCP of 2.5 seconds or less, an INP of 200 milliseconds or less, and a CLS of 0.1 or less. Each is checked at the 75th percentile of visits, separately for mobile and desktop. Poor starts above 4 seconds, 500 milliseconds and 0.25.
- Why did INP replace First Input Delay?
- INP replaced First Input Delay on 12 March 2024 because FID looked only at the first interaction, and only at the wait before its code started. INP looks at all taps, clicks and key presses in a visit. It also covers the full time until the next frame is painted, so slow handlers show up too.
- Can Lighthouse measure Core Web Vitals?
- Lighthouse's standard page-load report measures LCP and CLS but not INP, because nobody taps during the test. It reports Total Blocking Time as a stand-in. In Lighthouse 13.5.0, the INP audit runs only in timespan mode, where real or scripted interactions are recorded. Use field data from real visitors to judge the result.
- Does lazy loading help or hurt LCP?
- Lazy loading hurts LCP when you use it on the LCP image, because the browser waits to learn where the image sits before fetching it. In our Chromium 141 test, the request waited until a stylesheet that took 200 ms to arrive had loaded. It helps on images further down the page, since they stop competing for bandwidth.
- Why is my CLS bad in the field but fine in the lab?
- Field CLS is often worse because lab tests usually measure only during page load. Real visitors stay longer, scroll, and see late banners, ads and embeds that resize. Look at which element moved in your field data, then reserve space for it. Remember that your own CLS data comes from Chromium-based browsers only.
Sources and further reading
- Web Vitals: thresholds, the 75th percentile and the metric lifecycle (web.dev)
- Largest Contentful Paint: candidate elements and when reporting stops (web.dev)
- Optimize Largest Contentful Paint: the four LCP subparts and fetchpriority (web.dev)
- Browser-level image lazy loading: keep in-viewport images eager (web.dev)
- Interaction to Next Paint: thresholds, observed interactions and the 1-in-50 rule (web.dev)
- INP becomes a Core Web Vital on March 12 (web.dev blog)
- Optimize long tasks: the 50 ms definition and scheduler.yield (web.dev)
- Optimize Interaction to Next Paint: layout thrashing and DOM size (web.dev)
- Cumulative Layout Shift: shift scores, session windows and recent input (web.dev)
- Optimize Cumulative Layout Shift: images, ads, fonts and animations (web.dev)
- Event Timing API: 8 ms rounding and the 16 ms minimum threshold (W3C editor's draft)
- CrUX API: 28-day rolling average, updated daily (Chrome for Developers)
- Core Web Vitals report: URL groups and status rules (Search Console Help)
- web-vitals library: browser support per metric and iframe limits (GitHub README)