aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Web Font Optimization: font-display and FOUT/FOIT

A solid web font optimization strategy is the difference between a page that loads fast and one that loads with visual stability. When fonts reach the browser late, text either stays invisible (FOIT) or first draws in a fallback and then jumps as it swaps (FOUT). In this post I explain how to control both behaviours with font-display, preload, WOFF2 and subsetting, and how to protect your Core Web Vitals scores along the way.

The difference between FOUT and FOIT

Both acronyms describe the transition moment while a custom font loads:

  • FOIT (Flash of Invisible Text): the browser draws no text until the font arrives. The user sees a blank gap, and if the font is slow the content can stay invisible for seconds.
  • FOUT (Flash of Unstyled Text): the browser shows the text in a system fallback first, then redraws once the custom font lands. Content is readable immediately, but you get a visible shift when the glyph widths change.

FOUT is usually preferable to FOIT: the user starts reading without waiting. The real goal is to make that transition designed and jump-free.

Controlling behaviour with font-display

The font-display descriptor decides how the browser paints text while the font downloads, and it lives inside the @font-face rule:

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

What the values mean in practice:

  • swap: after a very short block period the fallback shows immediately and swaps once the custom font lands. Classic FOUT — text is never invisible.
  • block: text waits invisibly for a short period (around 3 s), then falls back. High FOIT risk.
  • fallback: very short block plus a short swap window. If the font arrives late it stays on the fallback; a good balance for logos and brand typography.
  • optional: the browser decides whether to use the font at all based on connection speed. It minimizes layout shift (CLS) and is ideal for body text.

For most content sites swap or optional are the safest choices.

Prioritizing critical fonts with preload

The browser only discovers a font after it has downloaded the CSS and found an element that uses it, which delays the font's start. With <link rel="preload"> you can kick off the download of your most critical font early:

<link rel="preload" href="/fonts/inter-var.woff2"
      as="font" type="font/woff2" crossorigin>

Two details matter. The crossorigin attribute is mandatory for fonts (even when serving from the same origin); otherwise the browser downloads the font twice. Second, only preload the one or two fonts actually used above the fold — preloading every weight wastes bandwidth and delays other resources.

Shrinking files with WOFF2 and subsetting

Format choice is one of the biggest wins. WOFF2 is supported in every modern browser today and compresses roughly 30% better than WOFF. You no longer need to serve legacy TTF/OTF or WOFF — WOFF2 alone is enough.

The second big win is subsetting: keeping only the characters you actually need. A full font can contain thousands of glyphs, and a Latin-only site never draws most of them. With unicode-range you tell the browser which range lives in which file:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

Now the browser never downloads the file if no character from that range appears on the page. You can generate subsets automatically with glyphhanger or fonttools' pyftsubset:

pyftsubset inter.ttf \
  --unicodes="U+0000-00FF,U+0131,U+0152-0153" \
  --flavor=woff2 \
  --output-file=inter-latin.woff2

Preventing layout shift (CLS)

The most annoying part of FOUT is the jump caused by the fallback and the custom font having different widths. Modern CSS largely solves this. With size-adjust, ascent-override and descent-override you can bring a fallback font close to your custom font's metrics:

@font-face {
  font-family: "Inter-fallback";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
}

If you then write font-family: "Inter", "Inter-fallback", sans-serif;, there is almost no shift when the custom font lands. Instead of computing these metrics by hand, tools like Fontaine can generate them for you.

Checklist

  • Serve WOFF2 only; drop legacy formats.
  • Load only the weights and styles you actually use, discard the rest.
  • Subset Latin text and split it with unicode-range.
  • Preload the one or two above-the-fold fonts (don't forget crossorigin).
  • Choose swap or optional for body text.
  • Align fallback metrics with size-adjust to drive CLS toward zero.

Frequently Asked Questions

Should I use font-display: swap or optional?

If reading the text immediately is the priority, use swap; your custom font will definitely appear. If layout stability and CLS score matter more, optional is safer, because the browser may never swap the font on slow connections. For most body text, optional for paragraphs and swap for headings is a good combination.

Is Google Fonts or self-hosting faster?

Self-hosting on your own domain is generally faster today. Browsers no longer share third-party cache across sites, so the old Google Fonts advantage is gone; connecting to an extra domain also adds DNS and TLS latency. Downloading the WOFF2 files and serving them from your own server with preload is both faster and cleaner for privacy.

Do variable fonts hurt performance?

Usually the opposite. A single variable font file replaces several weights you would otherwise download separately; if you use three or four weights, the total size often drops. Only if you use a single weight and style might a static subset be smaller.

Are your fonts slowing the page down or causing text to jump? Let's overhaul your site's load performance and typography together. Get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için