Web Fonts & Typography
Optimize web fonts: self-hosting, font subsetting, font-display strategies, variable fonts, and achieving consistent cross-browser typography.
Web fonts significantly impact performance and visual consistency. Best practice is self-hosting fonts (avoiding third-party DNS lookups), subsetting to include only needed characters, using `font-display: swap` to prevent FOIT, and leveraging variable fonts to reduce HTTP requests.
Self-host all fonts - third-party font services add latency and privacy concerns. Subset fonts to the character sets you use. Use variable fonts (one file, infinite weights). Preload critical fonts. Use `font-display: swap` as a default.
Workflow
- Self-host fonts correctlySelf-hosting eliminates third-party DNS lookups and gives you control over caching.
- Subset fonts to reduce file sizeFull font files contain all Unicode characters; you likely only need Latin characters, saving 80%+ on file size.
- Use variable fontsA single variable font file replaces multiple static weight/style files, reducing requests and size.
- Implement font-display strategyWithout `font-display`, browsers may show invisible text (FOIT) while the font loads.
- Establish a type scaleConsistent typography creates visual hierarchy and reduces decision fatigue.
Output Format
Self-hosted WOFF2, preloaded in HTML head, font subsetted for character range, variable font with weight range, `font-display: swap` + size-adjusted fallback, type scale as CSS custom properties.
Examples
Font face: `@font-face { font-family: "Geist"; src: url("/fonts/geist-var.woff2") format("woff2-variations"); font-weight: 100 900; font-display: swap; } @font-face { font-family: "Geist-fallback"; src: local("Arial"); size-adjust: 105%; ascent-override: 90%; descent-override: 22%; }`Fluid type scale: `html { font-size: clamp(15px, 0.75vw + 13px, 18px); } h1 { font-size: clamp(2rem, 4vw + 1rem, 4rem); }`Unicode-range subset: `@font-face { font-family: "NotoSans"; src: url("/fonts/noto-latin.woff2") format("woff2"); unicode-range: U+0000-00FF; } @font-face { font-family: "NotoSans"; src: url("/fonts/noto-cjk.woff2") format("woff2"); unicode-range: U+4E00-9FFF; }`Edge Cases & Gotchas
- iOS Safari and font-display: iOS Safari has partial support for `font-display: optional`; test on real iOS devices
- Emoji fonts: emoji rendering depends on OS emoji font (Apple Color Emoji, Noto Color Emoji); cannot subset or replace without significant complexity
- GDPR and Google Fonts: embedding Google Fonts URLs (fonts.googleapis.com) in HTML triggers cross-site requests - self-host to avoid GDPR compliance issues in the EU
When to Break These Rules
- For marketing landing pages where brand font appearance is critical and speed matters less, `font-display: block` with a very short timeout ensures the brand font is always shown, avoiding fallback flashes.
Reference Files
Font-display: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-displayFontsource: https://fontsource.org/Google Webfonts Helper: https://gwfh.mranftl.com/Variable fonts: https://web.dev/articles/variable-fonts