Core Web Vitals — narrowing causes from LCP / INP / CLS
When you receive the report "the site is slow", where do you start looking? Server logs, image sizes, JavaScript payloads. There are countless candidates.
Core Web Vitals is a tool for narrowing that search. The three metrics each correspond to a different family of causes, so knowing which one is bad tells you which places to suspect. In practice, deriving the cause from combinations matters more than memorizing definitions.
What you'll learn in this chapter
- What each of the three metrics measures, and where "poor" starts
- How to narrow down cause families from combinations of readings
- Why slow server responses are not the main cause of poor INP
- The difference between field data and lab data
The hydration discussion in Rendering patterns helps with understanding INP. Why INP's three segments end up being "main thread" stories is covered by Event loop.
What this chapter does not cover
The actual process of measuring and fixing LCP and CLS on this site is documented in a blog post. This chapter focuses on how to read the metrics.
- Japanese web fonts and Core Web Vitals — A record of identifying causes by measurement and fixing them
The three metrics and thresholds
| Metric | What it measures | Good | Needs improvement | Poor |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Loading speed | ≤ 2.5s | 2.5 – 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | Responsiveness to interaction | ≤ 200ms | 201 – 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | Visual stability | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |
Evaluation is done at the 75th percentile of page loads, segmented across mobile and desktop, not at the average. Averages cover up the situation of a subset of users being much slower, so the design is to see "are three out of four page loads good".
INP replaced FID (First Input Delay) as an official metric in 2024. FID measured only the wait before the first interaction, while INP measures every interaction during the page's lifetime, from the input to the screen update. To fold them into a single number, the slowest recorded interaction is taken (on pages with large numbers of interactions, the worst one is dropped for every 50). It isn't an average, so a single heavy interaction that happens rarely is enough to make the page poor.
LCP — until the main content becomes visible
The time until the largest element painted within the viewport (often a hero image or a heading text block) is displayed.
Cause families break down like this.
| Family | Examples |
|---|---|
| Slow server response | Heavy database queries, cache misses, distant origin |
| Slow resource loading | Huge images, uncompressed images, not using a CDN |
| Rendering blocked | Sync scripts in head, how web fonts are loaded |
| Only renderable on the client | Under CSR, main content doesn't appear without waiting for JavaScript execution |
If LCP is good, you can conclude that neither server response nor main content delivery is fatally slow. That matters for later triage. An LCP of 2.5s can still hide a TTFB of 2.0s, though, so if LCP sits near the lower bound of "good", check TTFB separately.
INP — from interaction to next paint
The time from when the user interacts until the resulting screen update paints. The target interactions are only click, tap, and key input — scroll, hover, and zoom are not included.
INP is the sum of three segments.
| Segment | What decides it |
|---|---|
| Input delay | Time the main thread is blocked by other work. Long tasks are the main cause |
| Processing time | Execution time of the event handler callback itself |
| Presentation delay | Layout recalculation and painting. Grows with large DOM and complex layout |
All three are about the browser's main thread. That connects to an important point. The mechanism where the main thread is a single thread and long work makes others wait is in Event loop.
Slow server responses are not the main cause of INP
INP measures "how long the next paint is blocked", not how long until the network response that starts after the interaction. Even if you press a button, call an API, and only update the screen after the response, what INP counts is from the press to the next painted frame. The wait for the network is not included.
So if INP is poor, don't suspect the server — suspect client-side JavaScript. Server slowness shows up in LCP or TTFB (time to first byte).
Typical INP degraders:
- Heavy computation or large array processing inside a click handler
- A gigantic state update that re-renders the whole screen
- Interactions during a window when hydration hasn't finished
- Third-party scripts occupying the main thread
CLS — accumulation of unexpected shifts
A score for how much elements have moved on their own while the page is open. It has no unit. It isn't the total across the whole page — it takes the largest burst within a 5-second window.
The score is the product of two ratios.
Layout shift score = impact fraction × distance fraction
- Impact fraction — how much of the viewport area was affected by shifted elements
- Distance fraction — the largest distance moved, divided by the longer side of the viewport
If an element occupying half the viewport slides down by 25% of the viewport height, that's 0.75 impact × 0.25 distance = 0.1875. The design is bigger elements moving farther are worse.
Causes concentrate into four types.
| Cause | What happens |
|---|---|
| Images or videos with no dimensions | Height 0 until loaded, then pushes the elements below down the moment they arrive |
| Ads, widgets, embeds | Resize themselves. Load timing also unpredictable |
| Content inserted after the fact | Pushed in ahead of existing content, everything after moves |
| Web fonts | Fallback and real fonts have different sizes, so line wrapping changes |
The countermeasure principle is common. Reserve the space before it arrives. For images, specify width and height attributes (or use aspect-ratio); for ad slots, reserve a minimum height.
Note that shifts within 500ms of a click, tap, or key press are excluded from measurement. The design deliberately doesn't punish shifts the user expected — like an accordion opening after a button press. Scrolling and dragging don't count, so content injected in response to a scroll is not excluded. What CLS cares about is only "unexpected shifts".
Narrowing down the cause from combinations
This is what you use most in practice. Because the three metrics correspond to independent families, the good/bad combination tells you where the cause is.
| LCP | INP | CLS | Suspect |
|---|---|---|---|
| Poor | Good | Good | Server response, heavy hero image, render-blocking |
| Good | Poor | Good | JavaScript running on interaction. Handlers, long tasks |
| Good | Good | Poor | Unsized late-arriving content. Images, embeds, insertions |
| Poor | Poor | Good | Large JavaScript bundle. Heavy both to load and execute |
| Good | Poor | Poor | Large DOM restructuring after interaction |
| Poor | Good | Poor | Image issues overall. Both heavy and unsized |
Reading example: LCP 1s (good), INP 520ms (poor), CLS 0.28 (poor).
- LCP good → server response and main content delivery are fast. Don't suspect the server
- INP poor → heavy work is running on the main thread on interaction. Suspect handlers and state updates
- CLS poor → something is arriving late and pushing existing content down. Likely lazy-loaded images or embeds with no dimensions specified
Concluding from these that "slow server-side processing is affecting INP through response wait" would be wrong. INP doesn't include server wait, and with LCP at 1 second we already know server response is fast.
Field data and lab data
Even for the same metric, there are two ways to measure.
| Field data | Lab data | |
|---|---|---|
| What it measures | Actual users' actual experience | Reproducible measurement under fixed conditions |
| Source | Chrome UX Report, web-vitals library | Lighthouse, DevTools |
| INP support | Measurable | Only from measurements that include interactions |
| Use | Understanding current state, verifying improvement | Identifying causes, comparing before/after fixes |
INP cannot be measured without real interactions. A normal Lighthouse page run only opens the page, so no INP comes out, and its score alone can't tell you "INP is fine". A measurement that includes interactions — the DevTools Performance panel, for instance — does produce INP in the lab.
CLS also needs care. Measuring on a fast network with a fast device can make lazy-loaded content arrive too soon to reproduce the shift. You need to throttle network and CPU to see it.
Common misconceptions
"If LCP is good, the whole site's performance is fine." — LCP only guarantees main content display. Responsiveness (INP) and visual stability (CLS) are separate families, so LCP being good while both are poor happens routinely.
"INP is bad because the server is slow." — INP is about the browser's main thread and doesn't include server-response wait. Server slowness shows up in LCP or TTFB.
"Scroll stutter degrades INP." — INP's targets are only click, tap, and key input. Scroll isn't included.
"CLS counts any shift." — Shifts within 500ms of a click, tap, or key press are excluded (scrolling doesn't count). Only unexpected shifts are counted.
"If Lighthouse's score is good, Core Web Vitals is good." — A normal Lighthouse score doesn't include INP. It can diverge from real-user field data.
Exercises
Q1. Results: LCP 3.8s (needs improvement), INP 150ms (good), CLS 0.05 (good). Where do you look first?
Answer and explanation
Answer: Server response time and the delivery path for main content.
Because INP is good, client-side JavaScript isn't heavy. Because CLS is good, there's no late-arriving content pushing things down. Only LCP is bad.
When only LCP is bad, investigate in this order.
- TTFB — is the server response itself slow? Heavy queries, cache misses
- Main element delivery — is the hero image gigantic, is it compressed, is it served from a CDN?
- Render-blocking — sync scripts or web fonts in
headblocking rendering?
There's a real example on this site where the way web fonts were loaded stalled LCP.
Q2. You get the report: "The API response after interaction takes 3 seconds, so INP is bad." Is this diagnosis valid?
Answer and explanation
Answer: It is not valid.
INP measures "from the interaction to the next painted screen". It does not include the completion of a network call started by the interaction. Even if the API takes 3 seconds, if you switch to a loading state right after the press, then that switch is what INP measures.
There's a related concern, though. Nothing changing for 3 seconds after a press is a UX problem. Frame it not as "the INP metric", but by design: show a loading state immediately on press, or use optimistic updates to show the result up front.
"Metric is bad" and "experience is bad" overlap but are not the same. Without understanding what each metric measures, countermeasures miss the mark.
Q3. CLS is 0.3 (poor). When you check in the dev-environment Chrome, you can't reproduce any shift. Why?
Answer and explanation
Answer: The network and device are too fast; late-arriving content isn't arriving "late".
The main cause of CLS is late-arriving content pushing existing content down. On a dev machine with a fast connection, images and ads arrive instantly, and the layout settles before pushdown occurs.
To reproduce, bring measurement conditions closer to real users.
- Network throttling in DevTools (Slow 4G, etc.)
- CPU throttling (4x – 6x)
- Reload with cache disabled
Then check whether lazy-loaded images, ad slots, and embeds have dimensions specified (width / height or aspect-ratio).
Summary
- Core Web Vitals is three metrics — LCP (loading), INP (responsiveness), CLS (stability) — evaluated at the 75th percentile
- Thresholds: good is LCP ≤ 2.5s / INP ≤ 200ms / CLS ≤ 0.1; poor is > 4.0s / > 500ms / > 0.25
- INP is a browser main-thread metric, and slow server responses are not its main cause. Server slowness shows up in LCP and TTFB (it can still matter indirectly, by stretching the wait for hydration)
- INP targets only click, tap, and key input. Scroll is not included
- The main cause of CLS is unsized late-arriving content. The countermeasure principle is to reserve the space before it arrives
- The three metrics correspond to independent families, so combinations of readings narrow down where to suspect
- INP doesn't come out of a measurement without interactions. CLS won't reproduce without throttled measurement conditions
- Japanese web fonts and Core Web Vitals — Fixing LCP and CLS by measurement — Record of measuring and fixing LCP and CLS on this site
- web.dev — Core Web Vitals
- web.dev — Interaction to Next Paint (INP)
- web.dev — Cumulative Layout Shift (CLS)
What to read next
- HTTP caching and CDNs — One of the main tools for improving LCP