Custom fonts give a brand its voice, but they are also behind three classic performance problems: invisible text while the font downloads, a jarring flash when it swaps in, and paragraphs that reflow and shift the layout. All three are fixable without giving up your typeface.
The three font problems
- Flash of invisible text: the browser hides text while the font loads. If the text is your LCP element, LCP waits too.
- Flash of unstyled text: text appears in a fallback font, then visibly changes when the custom font arrives.
- Layout shift: the custom font has different letter widths and line heights, so lines rewrap and push content around.
1. Self-host your fonts
Loading fonts from a third-party domain means an extra connection before the font can download, and browsers no longer share cached fonts between sites. Hosting WOFF2 files on your own domain removes that step. Most frameworks have a font helper that does this for you at build time.
2. Preload only the fonts above the fold
Browsers only discover a font once they have parsed the CSS and found text that uses it. Preloading starts the download straight away. Limit it to one or two files, usually the body and heading weights, because every preload competes for bandwidth.
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>Do not forget crossorigin
3. Choose the right font-display
- swap: show the fallback immediately and swap when the font arrives. Best for body text and headlines, since text is never invisible.
- optional: use the custom font only if it arrives almost instantly, otherwise keep the fallback for that page view. Zero layout shift, and the font is cached for the next page.
- block: hide text for up to three seconds. Avoid it except for icon fonts.
4. Match your fallback font to your web font
This is the step most sites miss. CSS descriptors let you resize a local system font so it takes up almost exactly the same space as your web font. When the swap happens, the text changes style but lines do not rewrap, so nothing shifts.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: "Inter", "Inter Fallback", system-ui, sans-serif;
}The exact values depend on the font. Tools such as Fontaine and the font helpers built into Next.js and Nuxt calculate them automatically.
5. Subset and consolidate
- Subset fonts to the characters you actually use. A Latin-only subset is often a fraction of the full file size.
- Use unicode-range so other scripts download only on pages that need them.
- Replace several static weights with one variable font file if you use three or more weights.
- Audit your CSS for weights and italics that nothing uses. Each one is a separate download.
6. Consider a system font stack
For body text, system fonts look native on every device and cost nothing to load. Many fast sites use a custom font for headings only and the system stack for everything else.
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}LightLoader prioritizes the critical font files on each page, which handles preloading for you. Combined with a metric-matched fallback, your fonts can load without visitors ever noticing the swap.