Font loading
Taming the flash of invisible text without slowing the page
Every `@font-face` declaration needs `font-display: swap`. Without it, the browser defaults to three seconds of invisible text.
## The performance pipeline
### 1. Preconnect to the font origin
```html
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
```
### 2. Preload critical fonts
```html
<link
rel="preload"
href="/fonts/inter-regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
```
Preload _only_ above-the-fold fonts. The `crossorigin` attribute is required even for same-origin fonts. Omit it and the browser downloads the font twice.
### 3. Choose a `font-display` strategy
| Value | Behavior | Use case |
| ---------- | --------------------------------------------------------------- | -------------------------------------- |
| `auto` | Browser decides. Usually 3s invisible text (FOIT) | Never use explicitly |
| `swap` | Show fallback immediately, swap when loaded (FOUT) | Default for most fonts |
| `fallback` | Short block (~100ms), short swap (~3s), then permanent fallback | Non-critical fonts |
| `optional` | Brief block; use cached font or permanently fall back | Slow networks, progressive enhancement |
`swap` is the right default.
## Taming the flash (FOUT)
`font-display: swap` trades invisible text for a visible flash when the webfont arrives. Tune the fallback to match the webfont's metrics with `size-adjust`, `ascent-override`, and `descent-override`:
```css
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
body {
font-family: "Inter", "Inter Fallback", sans-serif;
}
```
Fontaine and next/font automate this calculation.
## Common mistakes
**No `font-display` at all.** Defaults to `auto`, three seconds of invisible text.
**Preloading every weight.** Preload only above-the-fold weights.
**Loading weights you don't use.** If the design uses 400 and 600, don't load 300, 500, 700, 800, 900.
**Google Fonts without preconnect.** Preconnect to both `fonts.googleapis.com` and `fonts.gstatic.com`, or self-host.
## Font subsetting
**`unicode-range` subsetting** tells the browser to download a font file only when the page uses characters in that range:
```css
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F;
}
```
Google Fonts does this automatically, splitting each font into language-specific slices. Self-hosting? Use **glyphhanger** or **subfont** to generate subsets matching the characters you use.
Skip subsetting for variable fonts where it breaks axis interpolation, or sites with user-generated content in unpredictable scripts.