awesome-everything RU
↑ Back to the climb

Performance

Core Web Vitals: LCP, INP, and CLS

Crux Google''''s three field metrics quantify what users feel. Bundle size is the dominant lever for LCP and INP — this lesson maps bytes to thresholds.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

A team spends two sprints optimising their API response times. Server P99 drops from 400 ms to 90 ms. Google Search Console shows LCP is still “Poor” for 40% of mobile users. The API was not the bottleneck. The 800 KB JS bundle blocking the main thread was.

The three metrics and what they measure

Google’s Core Web Vitals are three field metrics — collected from real Chrome users via the CrUX dataset — that quantify distinct dimensions of user experience.

LCP (Largest Contentful Paint) — the time from navigation start to when the largest visible element above the fold is rendered. This is usually a hero image, an H1, or a background block. Target: under 2.5 s is “Good”; 2.5-4.0 s is “Needs Improvement”; above 4.0 s is “Poor”. Heavy JavaScript that blocks the main thread delays when the browser can paint — LCP suffers directly.

INP (Interaction to Next Paint) — introduced in March 2024, replacing FID. INP measures the worst-case latency from any user input (click, keypress, tap) to the next visual update during the full page lifetime. Target: under 200 ms is “Good”; 200-500 ms is “Needs Improvement”; above 500 ms is “Poor”. Long JS execution tasks on the main thread — including large bundle parse and execution — drive INP up.

CLS (Cumulative Layout Shift) — how much visible content shifts unexpectedly during load. Target: under 0.1 is “Good”; 0.1-0.25 is “Needs Improvement”; above 0.25 is “Poor”. Bundle size does not directly cause CLS; layout shifts come from unsized images, late-loading fonts, and JS that inserts above-fold content after initial paint.

MetricGoodNeeds ImprovementPoorBundle lever?
LCP< 2.5 s2.5 – 4.0 s> 4.0 sStrong — JS parse/execute blocks paint
INP< 200 ms200 – 500 ms> 500 msStrong — long tasks from large bundles
CLS< 0.10.1 – 0.25> 0.25Indirect — JS that inserts above-fold content

The 100-170 KB rule of thumb

Google’s Web Fundamentals guidance recommends keeping the initial JS under 100-170 KB gzip’d for fast first interactive on mid-range mobile. The math: 170 KB gzip’d decompresses to roughly 500 KB. V8 parses at 200-400 KB/sec on mid-range Android, so parse alone takes 1.5 s. Add compile and execute and the total is 2-2.5 s CPU cost. Add a 4G download at ~500 ms and you arrive at roughly 3 s to interactive — the upper bound that keeps LCP under 2.5 s. Every additional 100 KB beyond that adds roughly 300-500 ms to LCP on the median device.

Different routes can carry different budgets: a marketing homepage should be tight (50-100 KB), a dashboard medium (150-250 KB), admin tools more relaxed (300-500 KB). The homepage carries the tightest cap because it is the conversion entry point.

Three tools: Lighthouse, PageSpeed Insights, RUM

These tools measure the same metrics but from different vantage points.

Lighthouse runs a synthetic test — DevTools or CLI — with CPU throttling on the tester’s machine. Reliable for catching regressions in PRs. Not field data; it represents a single synthetic run.

PageSpeed Insights runs a Google-managed Lighthouse test plus overlays field data from the CrUX dataset — real aggregated metrics from Chrome users on your domain. Useful for public-facing analysis. Updates once per day.

Real User Monitoring (RUM) — your own sampling of real users via the Web Vitals JS library, Sentry, Datadog RUM, or similar. Most accurate, but requires setup and budget. Senior pattern: Lighthouse in CI for catching regressions before merge; RUM in production for regressions that CI misses; PageSpeed Insights quarterly for cross-reference. Each catches a different class of problem; none replaces the others.

Why this works

INP replaced FID (First Input Delay) in March 2024. FID measured only the first interaction; INP measures the worst across the full session. A page where the first click is fast but the fifth is sluggish scored “Good” on FID and “Poor” on INP. The change makes it harder to game the metric with a fast handler for the first event while hiding slow handlers everywhere else.

Quiz

A team's P75 LCP is 3.2 s on mobile. Server TTFB is 80 ms. Most likely cause?

Quiz

A route's INP is 380 ms. The bundle is 900 KB uncompressed. After code splitting the bundle to 200 KB for this route, INP drops to 150 ms. What improved and why?

Quiz

Why does CLS (Cumulative Layout Shift) NOT improve much from reducing JS bundle size?

Recall before you leave
  1. 01
    What do LCP, INP, and CLS each measure? Give the 'Good' threshold for each.
  2. 02
    Why does a fast server TTFB (80 ms) not guarantee a good LCP?
  3. 03
    What is the difference between Lighthouse and RUM for catching CWV regressions?
Recap

Core Web Vitals are field metrics collected from real Chrome users. LCP and INP both improve as JS bundle size decreases — parse and execute time set the floor for how fast a page can paint and respond. The 100-170 KB gzip’d rule of thumb maps to the math: each 100 KB beyond that adds 300-500 ms to LCP on mid-range mobile. CLS has its own separate root causes (unsized images, fonts, dynamic insertion). Lighthouse catches regressions pre-merge; RUM catches production drift. The next lesson covers code splitting — the primary technique for reducing per-route bundle size.

Connected lessons
appears again in159
Continue the climb ↑Code splitting: route-level, component-level, vendor splitting
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.