Measuring Core Web Vitals for real on a static site
Static sites have an unfair advantage in performance: no server thinking, no hydration, no API waterfall. Scoring 100 in Lighthouse is easy.
Which is exactly why the number misleads.
Lab is not field
Lighthouse runs a simulation: one machine, one network, one cold load. The Core Web Vitals Google actually uses come from CrUX โ real users, real devices, real networks, at the 75th percentile.
The two disagree often, and the field wins.
| Measurement | Where it lives | What it answers |
|---|---|---|
| Lighthouse | your terminal / CI | “did I regress since last commit?” |
| CrUX | aggregated real data | “are my visitors suffering?” |
| Your own RUM | your site | “which pages, which devices?” |
The three metrics, one sentence each
LCP โ when the largest visible element finished painting. On a blog it is almost always the title or the first image. A badly configured web font delays it more than any image will.
INP โ how long the page takes to respond to an interaction. Static sites rarely fail here, unless a third-party script is holding the main thread.
CLS โ how much content jumps during load. Cause number one: images without
width/height. Cause number two: a font swap that changes text height.
Measuring on your own site
The official library fits in a few lines and reports real numbers:
import { onLCP, onINP, onCLS } from "web-vitals";
function send(metric) {
navigator.sendBeacon("/vitals", JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
path: location.pathname,
}));
}
onLCP(send);
onINP(send);
onCLS(send);sendBeacon does not hold navigation โ the payload goes out even after the
user has left the page.
What to fix first on a static site
- Fonts.
font-display: swap,preloadonly the files used above the fold, and serve them from your own domain. - Image dimensions. Always
widthandheight, even with responsive CSS on top. - Third-party scripts. Each one is an INP risk you do not control.
Optimize from the inside out: first what you serve, then what you borrow.
The reference that keeps up with metric definition changes is web.dev/vitals.