Web Performance

Web fonts are still the easiest performance win on most sites

Five years after variable fonts shipped and ten years after WOFF2 became universal, the average site is still loading fonts wrong. Here is the short version of how to get it right.

The Wux Webtools Team The Wux Webtools Team 4 min read
Editorial illustration of a single bold letter glyph with subtle weight axis lines, soft green palette
Table of contents
  1. The pattern that keeps appearing
  2. Use one variable font instead of six static ones
  3. Set a sensible `font-display`
  4. Match your fallback metrics
  5. Self-host
  6. When to skip custom fonts entirely
  7. The honest summary

The pattern that keeps appearing

If you audit a random ten production websites today, you will find roughly the same font situation on most of them:

  • Six to ten font files loaded on the home page
  • All of them in WOFF2 (good) but with no font-display strategy (bad)
  • Several weights and styles that are not used anywhere on the page
  • The whole stack served from a third-party domain (Google Fonts, usually) with all the DNS, TLS and privacy cost that entails
  • No unicode-range subsetting, so every visitor downloads the Cyrillic and Greek glyphs even if the page is in English

This is not a problem unique to small sites. Plenty of well-funded marketing pages still load 600 KB of fonts before the first paragraph is paintable. Once you start noticing the pattern, you cannot unsee it.

The fix is not exotic. It is a handful of well-understood techniques that have been in browsers for years.

Use one variable font instead of six static ones

Comparison of six static font files versus one variable font file with script-based subsetting for Latin, Cyrillic, and Greek
InfographicFrom six static font files to one variable font — One variable file can replace a pile of weights and italics with far fewer bytes

If you are loading Inter Regular, Inter Medium, Inter SemiBold, Inter Bold and their italics, you are downloading roughly six times more bytes than you need. A single Inter variable font covers the entire weight axis (and slant, in some builds) in one file that is barely larger than two static weights.

The browser support story has been settled for years — variable fonts work everywhere that matters. The remaining hesitation is mostly inherited muscle memory from the static-font era.

A practical rule of thumb: one variable font file per script, per family. Latin in one file, Cyrillic in another, Greek in a third, loaded conditionally with unicode-range. That is it.

Set a sensible font-display

The default behaviour when a custom font is still loading is to show nothing — invisible text — for up to three seconds. This is the worst possible default. Users see a blank page and assume something is broken.

Add this to every @font-face rule:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

swap shows the fallback font immediately and swaps in the custom font when it loads. The user can read the page from millisecond zero. The trade-off is a brief layout shift when the swap happens, which you mitigate with the next technique.

Match your fallback metrics

A flash of unstyled text only feels jarring when the fallback and the custom font have very different metrics. Modern CSS fixes this with size-adjust, ascent-override and friends:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

With fallback metrics tuned, the swap is barely perceptible — words occupy the same horizontal space before and after the custom font arrives.

Google's --allow-fallback-font-metrics work is now baseline browser support, and there is a tooling pipeline (the fontaine library and similar) that calculates the right numbers for you in seconds.

Self-host

Four-step self-hosted font pipeline: get WOFF2 variable file, subset by script, serve from same origin with immutable cache, preload critical weight
InfographicSelf-hosting web fonts: the simple delivery pipeline — A same-origin font pipeline cuts extra origin cost and removes the privacy question

Google Fonts is convenient, free, and brings two genuine costs:

  1. A second TLS handshake. Even with HTTP/3 and connection coalescing, an extra origin is rarely free.
  2. A privacy and compliance question. Several European courts have ruled that loading Google Fonts from fonts.gstatic.com constitutes transmitting personal data (the IP address) to a third party. Self-hosting removes the question entirely.

The download-and-host pipeline is:

  1. Get the WOFF2 file (variable build if it exists)
  2. Subset it to the scripts your audience actually uses
  3. Serve it from the same origin as the rest of your site, with a long Cache-Control: max-age=31536000, immutable
  4. Preload the most critical weight: <link rel="preload" as="font" type="font/woff2" href="/fonts/inter-var.woff2" crossorigin>

That is the whole pipeline. It takes an afternoon for an existing site, and you almost never go back.

When to skip custom fonts entirely

It is worth saying out loud: not every site needs a custom font. The system font stack is now genuinely beautiful on every operating system:

font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Inter, sans-serif;

The weight is right, the metrics are right, the rendering is tuned for the device, and the bytes-on-the-wire cost is exactly zero. For a tools site, an internal app, a portfolio that prioritises content, or anything aimed at users on slow networks, the system stack is the correct answer.

<!-- tool-cta:start -->

💡 Try this: Convert your TTF or OTF files to modern WOFF2 with ready-to-use CSS using the Webfont Generator, which is the format swap the post recommends.

<!-- tool-cta:end -->

The honest summary

Four-step checklist for faster web fonts: variable font, font-display swap with matched fallback metrics, self-host with cache and preload, or use system stack
InfographicThe 4-step web font performance fix — A compact playbook for reclaiming several hundred kilobytes and improving perceived performance

For a typical small site, four steps cover the entire web-font performance story:

  1. One variable font per family per script
  2. font-display: swap with matched fallback metrics
  3. Self-hosted with long cache headers and a preload for the critical weight
  4. Or skip fonts entirely and use the system stack

Do this once and you reclaim several hundred kilobytes from the home page, win a few hundred milliseconds of perceived performance, and remove a third-party dependency on the way. Few changes have a better effort-to-payoff ratio.

Frequently asked questions

Are variable fonts supported everywhere?
Yes — every browser that matters has shipped variable font support for years. The hesitation you sometimes hear is muscle memory from the static-font era; it is not a real compatibility concern in 2026.
Should I subset my fonts manually?
For Latin-only sites, yes — a Latin subset is roughly half the size of the full multilingual file. For multilingual sites, use `unicode-range` to load each script as a separate file, so visitors only download what their content actually needs.
Why does font-display: swap sometimes cause an ugly flash?
Because the fallback font's metrics differ from the custom one, so words reflow when the swap happens. Use `size-adjust`, `ascent-override` and `descent-override` on the fallback to match the custom font's metrics — the flash becomes nearly invisible.
Is Google Fonts really a privacy issue?
Several European court decisions have ruled that loading from `fonts.gstatic.com` transmits the visitor's IP to a third party without consent, which is a GDPR violation in some interpretations. Self-hosting removes the question completely and is usually faster anyway.

Sources & further reading

  1. MDN — `@font-face` and `font-display`
  2. MDN — Variable fonts guide
  3. web.dev — Reduce web font size
About the author
The Wux Webtools Team

Last updated:

Keep reading