Performance
Fonts: preload, swap and CLS, measured
Without font-display LCP is 644 ms; with swap it drops to 280 ms and CLS stays at 0.0028. Preload made FCP worse, from 280 to 456 ms.
Key points
- Without font-display the text waits for the file: LCP 644 ms against 280 ms with swap.
- CLS does not change between no font-display, swap and preload: 0.0028 in all three.
- font-display: optional takes CLS to 0 and gives up the font when it arrives late.
- Preload made FCP 176 ms worse because the font competed with the CSS for bandwidth.
Without font-display the text with a web font stayed invisible until an LCP of 644 ms; with swap LCP is 280 ms at the same CLS of 0.0028. In our test preload made FCP worse, from 280 to 456 ms, because the font took bandwidth from the CSS.
The four variants
| Variant | FCP | LCP | CLS | Load |
|---|---|---|---|---|
| No font-display | 280 ms | 644 ms | 0.0028 | 626 ms |
| font-display: swap | 280 ms | 280 ms | 0.0028 | 626 ms |
| swap + preload | 456 ms | 456 ms | 0.0028 | 606 ms |
| font-display: optional | 284 ms | 284 ms | 0 | 629 ms |
The 364 ms between 644 and 280 is the block period: the browser hides the text until the file arrives, and LCP waits for that text. With swap the text appears at once in the fallback font and the measured CLS stays identical.
The CSS
@font-face {
font-family: "Inter";
src: url("/fonts/inter-400-latin.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
html {
font-family: "Inter", system-ui, sans-serif;
}
Serve the file from your own origin with a long cache and immutable: the one on our site answers with max-age=31536000 and weighs 48,256 bytes. Preloading the weight used above the fold needs crossorigin, otherwise the browser downloads the file twice.
Why preload made FCP worse
Preload started the font download earlier but also pushed the CSS back: on a 1.6 Mbps link two 48 KB files compete, and first paint went from 280 to 456 ms. Preload pays off when the font really is in the first screen and the CSS is already small; it is not a universal rule.
The rules that hold
- Always set
font-display: swaporoptional: without it the text stays invisible for the block period. Source: web.dev, CC BY 4.0. - Two weights per family at most. Regular alone is 48,256 bytes in woff2. Source: MDN, CC BY-SA 2.5.
- Preload only the weight used above the fold, with
crossorigin. Source: MDN, CC BY-SA 2.5. - Close the gap with
size-adjustandascent-overrideon the local fallback, not with an arbitraryline-height.
The limitation to name
The 0.0028 CLS is small because Times and Inter have close metrics on this text. A wider fallback produces a bigger jump, and a large heading moves far more than a paragraph. The test uses one font and one page: the choice between swap and optional depends on what the typeface is worth, not only on the number.
Where to continue
The EneUI catalogue indexes 111,985 components, for instance gooey-text-morphing, and the grid of 12,808 icons such as icon-abc. Read font-display and CLS, the preload strategy, what causes CLS and how to set a performance budget.
Frequently asked questions
Is font-display: swap always the right call?
It is the default for most sites: text appears at once and the font follows. For logos and brand headlines, optional is worth considering.
Why can preload make things worse?
Because it competes with the resources that block rendering. In the test FCP went from 280 to 456 ms with preload on.
How do I reduce the font's CLS?
Use a local fallback whose metrics resemble the web font, tuned with size-adjust, ascent-override and descent-override. A fixed line-height is not enough.
How many font weights do I need?
Two in most cases, regular and bold. Every weight is a 40-50 KB file and on mobile that is bandwidth you do not get back.
Self-host or Google Fonts?
Self-hosting removes a third-party host, a handshake and a privacy question. In exchange you update the files yourself when the font changes.