Made by Dhawal Made by Dhawal

Core Web Vitals for Architects: Stop Optimizing Wrong

Lab scores lie. Field data tells the truth. What LCP, INP and CLS really measure, why a Lighthouse 100 can hide failing users, and how to measure before you optimise.

Told by Vayu, with Kai asking the questions

Core Web Vitals for Architects: Stop Optimizing Wrong

Vayu: I'm the wind, Kai. I don't care how fast your page loads on your laptop. I care how fast it loads on a three-year-old phone, on a train, in the rain.

But our Lighthouse score is 100!

I've spent over twenty years building frontends, and I keep seeing the same cycle. A metric turns red. The team runs a "perf sprint". The metric turns green. Six months later, it's red again.

The problem is that teams treat Core Web Vitals as a checklist of tricks, when they're really symptoms of architectural decisions. A slow LCP isn't caused by a missing preload tag. It's caused by how you render, where you host, and what you ship. Fix the architecture, and the metrics follow.

The Mirage of the perfect score

KaiLighthouse says 100 on every page. We're the fastest site on the internet!


A week later, Search Console flags the product pages: LCP poor, 4.2 seconds. Same pages, same code. How?

VayuYou measured on your laptop, on office Wi-Fi, with nothing else running. Your customers are on mid-range Android phones and patchy 4G. Your 100 was a mirage.

What Core Web Vitals actually measure

Three metrics are the Core Web Vitals. Two more are diagnostic: they don't count on their own, but they explain why the first one is slow. TTFB feeds FCP, and FCP feeds LCP, so this series covers all five.

MetricWhat it measuresGoodPoor
LCP · Largest Contentful PaintWhen the main content is visible (loading)≤ 2.5 s> 4 s
INP · Interaction to Next PaintHow quickly the page responds to taps, clicks and keys (responsiveness)≤ 200 ms> 500 ms
CLS · Cumulative Layout ShiftHow much the page jumps around (visual stability)≤ 0.1> 0.25
TTFB · Time to First Byte (diagnostic)How long the server takes to start responding≤ 0.8 s> 1.8 s
FCP · First Contentful Paint (diagnostic)When anything first appears≤ 1.8 s> 3 s

Every threshold is judged at the 75th percentile of real page loads. In other words, three out of four visits must be good, not just the average one. INP replaced First Input Delay as a Core Web Vital in March 2024.

The lie your Lighthouse score is telling you

 Lab dataField data
ToolsLighthouse, WebPageTest, DevToolsCrUX, RUM (real-user monitoring)
Who loads the pageOne simulated device and networkYour real users: every device, network and cache state
ResultOne reproducible runA distribution, judged at the 75th percentile
Best forDebugging and catching regressions in CIKnowing what users experience, and what Google ranks

Google's ranking signal uses field data. So a team can celebrate a Lighthouse 100 while CrUX shows a failing LCP for the mid-range Android users who make up much of their traffic. Lab data is a fantastic debugging tool. It is not the goal.

KaiSo Lighthouse isn't useless. I was just using it as the finish line instead of the microscope.

Does this actually affect ranking (and revenue)?

Yes, but probably not the way you think. Core Web Vitals are a ranking factor, mostly acting as a tiebreaker between pages of similar relevance. Great vitals won't rescue weak content.

The stronger case is conversion. Industry case studies repeatedly tie faster loading and snappier interactions to more completed checkouts, sign-ups and page views. Ranking is the bonus, not the reason.

Measure first, optimise second

There are three tiers of measurement, in order of effort.

1. CrUX: free field data, zero setup

The Chrome UX Report powers the field data in PageSpeed Insights and the Core Web Vitals report in Search Console. It covers a trailing 28-day window, only at origin or URL level, and only for pages with enough Chrome traffic. Start here: it's what Google sees.

2. The web-vitals library: your own real-user data

Google's small web-vitals library measures LCP, INP and CLS exactly the way Chrome does, from your real visitors, and lets you send the results to your own analytics. It's about an afternoon of work:

vitals.js
import {
    onLCP,
    onINP,
    onCLS
} from 'web-vitals';
function sendToAnalytics({
    name,
    value,
    rating,
    id
}) {
    const body = JSON.stringify({
        name,
        value,
        rating,
        id,
        page: location.pathname
    }); // sendBeacon survives the page closing; fall back to fetch if it's unavailable
    (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) || fetch('/analytics', {
        body,
        method: 'POST',
        keepalive: true
    });
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

3. RUM with attribution: find the culprit

Knowing LCP is slow isn't enough; you need to know which element and which phase. The attribution build of web-vitals (or a commercial RUM product) tells you the LCP element, the slow interaction, and the script that blocked the main thread:

vitals-attribution.js
import {
    onLCP,
    onINP
} from 'web-vitals/attribution';
onLCP(({
    value,
    attribution
}) => {
    console.log('LCP', value, 'element:', attribution.target);
});
onINP(({
    value,
    attribution
}) => {
    console.log('INP', value, 'slow interaction on:', attribution.interactionTarget);
});

VayuNow you're listening to the wind instead of guessing its direction. Every post in this series starts with one question: what does the field data say?

What's coming in this series

  • LCP: what LCP really measures, the four phases of loading, and image LCP decisions.
  • CLS: the maths behind layout shift, and layouts that can't shift.
  • FCP: the critical rendering path, and the third-party scripts that block it.
  • TTFB: CDNs, caching, redirects, and the real cost of server-side rendering.
  • INP: the main thread, and why this is the hardest of the five.
  • Conclusion: how the metrics affect each other, and how to stop regressions coming back.

No "10 tips to speed up your site". Real traces, real regressions, real trade-offs.

KaiField data first, Lighthouse for debugging. No more mirages!

Acharya's Rule #6

Measure first, optimise second. Your real users are the only score that counts.

Next up: Series 1, Post 1 — What LCP Really Measures (and How the LCP Element Is Chosen)

Frequently asked questions

What are the three Core Web Vitals?+

Largest Contentful Paint (LCP) for loading, Interaction to Next Paint (INP) for responsiveness, and Cumulative Layout Shift (CLS) for visual stability. TTFB and FCP are supporting diagnostic metrics.

What are good Core Web Vitals scores?+

LCP of 2.5 seconds or less, INP of 200 milliseconds or less, and CLS of 0.1 or less, measured at the 75th percentile of real page loads.

Why is my Lighthouse score high but my Core Web Vitals failing?+

Lighthouse is lab data: one simulated device and network. Core Web Vitals in Search Console come from field data, from real users on real devices and networks. Slower phones and networks often push the 75th percentile past the threshold.

Do Core Web Vitals affect Google rankings?+

Yes, as part of the page experience signals, but mostly as a tiebreaker between pages of similar relevance. The bigger impact is usually on conversions and user experience.

How do I measure Core Web Vitals for real users?+

Start with CrUX data in PageSpeed Insights and Search Console. Then add Google’s web-vitals library to send LCP, INP and CLS from real visitors to your analytics, and use its attribution build to find the slow element or interaction.