Fixing Cumulative Layout Shift: reserve space for everything
CLS is the most fixable Core Web Vital. Almost every point of it comes from an element that did not tell the browser how big it would be.
Optimise web fonts by self-hosting WOFF2 files, subsetting to the characters you use, preloading only the fonts needed above the fold, choosing font-display: swap with matched fallback metrics, and using a variable font instead of multiple static weights where you need three or more.
A typical family with four weights in WOFF2 is 200–400KB. Subsetted to Latin characters and reduced to the weights actually used, the same family is often under 60KB. On a slow connection that difference is a second of invisible or unstyled text.
It controls what the browser shows while a font is loading — nothing, a fallback, or the fallback permanently.
| Value | Behaviour | Use for |
|---|---|---|
| swap | Fallback immediately, swap when loaded | Body text — the usual choice |
| optional | Fallback; use the font only if nearly instant | Best CLS; font may not appear on first visit |
| fallback | Short block, then fallback, brief swap window | A compromise |
| block | Invisible text until loaded (up to 3s) | Rarely justified |
| auto | Browser default, usually block-like | Avoid — be explicit |
swap avoids invisible text but causes a reflow when the real font arrives. optional eliminates the shift entirely at the cost of sometimes not using your font on a first visit. Which is right depends on whether brand typography or layout stability matters more for that page.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2-variations");
font-weight: 100 900;
font-display: swap;
}
/* A fallback tuned to occupy the same space as Inter */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
:root { --font-sans: "Inter", "Inter Fallback", system-ui, sans-serif; }Framework font loaders automate this. next/font, for example, self-hosts the files, generates a metric-matched fallback and removes the external request in one step — which is why using it is almost always better than a manual @font-face block.
<link rel="preload" href="/fonts/inter-var.woff2" as="font"
type="font/woff2" crossorigin>Yes, in nearly all cases. Browser cache partitioning means a font fetched from a third-party CDN is no longer shared across sites, so the old argument for using a public font host has gone. Self-hosting removes a DNS lookup, a connection and a third-party dependency, and it avoids the privacy and regulatory questions that come with sending visitor IP addresses to another provider.
No. WOFF2 is supported by every browser in current use. Shipping WOFF and TTF fallbacks doubles your storage for no practical benefit.
Not for one or two weights — a single static weight is usually smaller. They win from around three weights onwards, and they enable fluid weight interpolation.
Tools like glyphhanger or pyftsubset generate a reduced file containing only the character ranges you need. For Latin-only content this typically removes the majority of the file.
Absolutely. Zero bytes, no shift, and native rendering on every platform. Many well-known products use system fonts deliberately.
ROVQIX Engineering
Engineering team, ROVQIX
The ROVQIX engineering team builds and maintains web platforms, APIs and infrastructure for clients across SaaS, ecommerce and enterprise. These notes come out of real production work — deploys, incidents, migrations and audits.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
CLS is the most fixable Core Web Vital. Almost every point of it comes from an element that did not tell the browser how big it would be.
LCP is not one number, it is four phases. Optimising the wrong one is why so much performance work produces no measurable change.
Performance work that happens once decays within two quarters. A budget makes it a constraint every pull request has to respect.
No spam. Just the occasional case study and craft breakdown.