Core Web Vitals, demystified: what LCP, INP, and CLS actually measure
The three Vitals are field metrics, not lab numbers. Here is what each one captures, why a Lighthouse 100 can still ship a slow site, and the three fixes that move the needle in most audits.
by Seitenbefund Workshop
Core Web Vitals are field metrics. Google aggregates them from real
Chrome users via the Chrome User Experience Report; they are not the
numbers your local Lighthouse run produces. Conflating the two is the
single most common mistake we see in audits — teams optimize for the
lab, the field stays bad, the rankings drift down, and nobody can
explain why.
Largest Contentful Paint records when the largest image or text
element in the initial viewport renders. The "good" threshold is 2.5
seconds at the 75th percentile of real users.
The patterns that drive bad LCP, in order of frequency:
A hero image with no fetchpriority="high". The browser walks the
preload queue: CSS, fonts, sometimes a Google Tag Manager script,
and only then the JPEG. On a mid-range Android over LTE that
ordering alone costs 800 ms.
An LCP element that does not exist in the initial HTML. Client-only
React apps that render the hero after hydration regularly post LCP
at 4 s+. Server rendering or static HTML fixes this without any
JavaScript optimization.
Missing srcset. A 2400 px hero shipped to a 390 px iPhone takes
longer to decode than to download.
The single highest-leverage change: identify the LCP element in
DevTools (Performance panel, the LCP marker), mark it with
fetchpriority="high", and ship a srcset that covers at least
mobile, tablet, and desktop widths.
Interaction to Next Paint replaced First Input Delay in March 2024.
The substantive change: FID measured only the first interaction's
input delay. INP measures the worst interaction across the whole
session, end-to-end (input delay plus processing plus presentation).
"Good" is 200 ms at p75.
Schema has 800+ types. Most small business sites need three. Here are the ones that move the needle, the ones we delete in every audit, and the validation steps that matter before shipping.
Three rules from GDPR Art. 7 and the ePrivacy Directive that most banners break, the EuGH case that pinned the equivalence requirement, and a minimal pattern that satisfies all three.
The dominant cause of bad INP scores is long tasks on the main
thread. A click handler that synchronously filters and transforms a
5,000-row list will block the main thread for 250 ms or more. The
fix is to break the work up.
// Bad: blocks the main thread for the full filter+map
function onClick() {
const result = items.filter((i) => heavyCheck(i)).map(transform)
setState(result)
}
// Better: yield to the scheduler so the browser can paint
function onClick() {
scheduler.postTask(
() => {
const result = items.filter((i) => heavyCheck(i)).map(transform)
setState(result)
},
{ priority: 'user-blocking' },
)
}
scheduler.postTask has been stable since Chrome 94 and is the
clean way to break work into yieldable chunks. For older browsers, a
setTimeout(fn, 0) is the historical workaround — it works, it
just doesn't communicate priority to the browser.
Cumulative Layout Shift is the sum of all unexpected layout shifts
after the first render. The threshold is 0.1.
Three sources dominate every audit:
Images without explicit dimensions.<img> tags missing
width and height attributes (or a CSS aspect-ratio). The
reflow when the image arrives shifts everything below it.
Web fonts swapping in.font-display: swap shifts every
text block when the web font loads. font-display: optional
gives the browser permission to skip the swap if the font
wasn't already cached, which is usually what you want for body
copy.
Cookie banners and ad slots inserted late. The pattern is
always the same: the team reserves no vertical space, the banner
pops in 800 ms after first paint, every header below jumps by
80 px. CLS over 0.3 is routine.
The fix for category 1 is trivial and lands a CLS under 0.1 in nearly
every audit we run: write width and height on every <img>
tag, and let CSS scale with height: auto.
Vitals say nothing about the cost of a bundle on a slow connection,
nothing about memory pressure, nothing about the third-party scripts
your tag manager might be loading. A site with perfect Vitals can be
unusable on a Pixel 4a over 3G if it ships 2 MB of JavaScript. The
Vitals are a floor, not a proof of quality.
If you fix LCP, INP and CLS often follow. A hero with
fetchpriority="high" and explicit dimensions shifts the render
profile so far forward that the rest of the JavaScript has time to
parse before the user clicks anything. One change, three metrics
move.
To see what your domain ships today, run the
Seitenbefund short check — passive measurement,
no installation, results in minutes.