Custom web fonts define brand identity, but they are also render-blocking. If delivered naively from origin, fonts create two infamous user experiences: FOIT (Flash of Invisible Text) and FOUT (Flash of Unstyled Text). When you serve fonts through a CDN, you have the infrastructure to eliminate both — if you configure caching, headers, and CSS correctly.
This guide breaks down the tradeoffs between FOUT and FOIT, how font-display controls the browser timeline, and the exact CDN delivery pattern that minimizes Cumulative Layout Shift (CLS) and Largest Contentful Paint (LCP) impact.
FOIT vs. FOUT: What Actually Happens
Browsers need text to render before fonts arrive. What they do in that gap defines the experience:
- FOIT: Browser hides text (invisible) for up to 3 seconds while waiting for the web font. If the font fails or is slow, users see nothing. Great for avoiding layout shift, terrible for perceived performance and accessibility.
- FOUT: Browser shows fallback system font immediately, then swaps to the web font when it loads. Content is instantly readable, but you get a visible reflow and style jump.
Neither is ideal by default. The goal is a controlled swap — instant readable text with minimal visual shift — achieved through CDN-optimized delivery and proper font-display.
Figure 1: FOIT hides text for up to 3s. Uncontrolled FOUT swaps late. Optimal strategy preloads from CDN and swaps within ~100ms with a matched fallback.
The font-display Spectrum
font-display inside @font-face tells the browser how long to block and how long to swap. Your CDN cannot fix a bad font-display choice — cache hits will still trigger FOIT if you use block.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
| font-display | Block Period | Swap Period | Best For |
|---|---|---|---|
| block FOIT risk | 3s invisible | Infinite | Logo / icon fonts where fallback is unacceptable |
| swap Recommended | ~100ms | Infinite | Body text, headings — instant readability |
| fallback | ~100ms | ~3s | Secondary fonts where brief FOUT is okay |
| optional Fastest | ~100ms | None | Performance-critical pages; uses cache or fallback only |
| auto | Browser default (usually block) | Varies | Avoid — unpredictable across browsers |
For most content sites, font-display: swap is the optimal default, combined with a well-matched fallback to reduce CLS. For hero text where layout shift is costly, pair swap with size-adjust and CDN preloading. optional is powerful for repeat visits — if the font is already in the CDN edge cache and HTTP cache, it swaps; otherwise it avoids a late swap entirely.
Learn how edge delivery affects overall metrics in our overview of CDN benefits for performance and security.
Optimal CDN Font Delivery: 5 Layers
1. Serve from Edge with Long-Lived, Immutable Caching
Fonts are ideal for aggressive caching — they are versioned and immutable. Serve WOFF2 only (30% smaller than WOFF) and fingerprint filenames like inter-v12-latin-400.woff2.
Set these headers at origin and propagate to the CDN:
# Nginx / CloudFront / Cloudflare Worker origin headers
Cache-Control: public, max-age=31536000, immutable
Access-Control-Allow-Origin: * # Required for cross-origin fonts
Content-Type: font/woff2
Timing-Allow-Origin: *On the CDN, enable Origin Shield and Tiered Cache so a single origin fetch populates all edges. This is especially important for fonts because a cache miss during LCP is far more damaging than for an image. For cost planning, see CDN cost optimization to balance long TTLs with purge strategies.
2. Preload Critical Fonts (Only 1-2 Files)
Preload eliminates the discovery delay — without it, the browser only discovers fonts after CSS is parsed. Preload from the same CDN hostname as your CSS to reuse the connection.
<!-- In <head> — must match @font-face URL exactly -->
<link rel="preload" href="https://cdn.flyfishnwa.com/fonts/inter-v12-latin-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="https://cdn.flyfishnwa.com/fonts/inter-v12-latin-700.woff2" as="font" type="font/woff2" crossorigin>
/* Preconnect to CDN if fonts are on separate domain */
<link rel="preconnect" href="https://cdn.flyfishnwa.com" crossorigin>3. Use @font-face with swap and size-adjust
Reduce CLS by matching fallback metrics to your web font. Modern CSS lets you adjust fallback size, ascent, and line gap:
@font-face {
font-family: 'Inter';
src: url('https://cdn.flyfishnwa.com/fonts/inter-v12-latin-400.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}
/* Metric-matched fallback to minimize CLS */
@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;
}Generate overrides with tools like Fontpie or Capsize. A well-tuned fallback can cut CLS from 0.15 to <0.02 even with swap.
4. Subset and Self-Host — Don’t Proxy Google Fonts
Self-hosting on your CDN outperforms proxying Google Fonts for three reasons: you control caching headers, you avoid an extra connection to fonts.googleapis.com, and you can subset aggressively.
- Subset by language: latin vs latin-ext can save 60-80KB per weight.
- Use unicode-range to let the browser download only needed subsets.
- Convert to WOFF2 only; drop EOT/TTF.
If you must use a third-party font service, use CDN edge computing to cache the CSS response at the edge and rewrite it to your domain.
5. Fix CORS and Compression
Fonts fail silently with CORS errors. Your CDN must return:
Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-originEnable Brotli (level 11) at the edge — WOFF2 is already compressed, but font CSS benefits. Ensure HTTP/2 or HTTP/3 is enabled so preloaded fonts multiplex without head-of-line blocking. For high-traffic sites, pair this with smart routing via CDN load balancing algorithms to keep edge latency <20ms.
Measuring Success
Validate your strategy with field and lab data:
- Lighthouse / PageSpeed: Watch “Ensure text remains visible during webfont load” and CLS. Preload should remove that audit warning.
- WebPageTest: Filmstrip should show text at ~0.8s, not 2.5s. Check waterfall — font should start at high priority before CSS finishes.
- CrUX / RUM: Track CLS and LCP by font cache hit rate. A CDN with 98%+ edge hit rate typically improves LCP by 200-400ms vs origin.
For e-commerce where font-induced CLS directly hurts conversion, apply the same edge priority logic described in CDN for e-commerce.
Common Pitfalls to Avoid
- Preloading with wrong crossorigin: Missing crossorigin causes double download — browser fetches preload anonymously but needs CORS for font.
- @import for fonts: Blocks CSS parsing. Always use <link> or inline @font-face in critical CSS.
- No font-display: Defaults to block (FOIT). Explicitly set swap or optional.
- Cache-Control: no-store: Some origins send this for fonts — override at CDN edge or fix origin.
- Over-subsetting: Removing characters needed for user-generated content causes tofu (□) glyphs. Test with real content.
When fonts are delivered correctly from the edge — immutable, preloaded, swapped with metric-matched fallbacks — users get instant readable text and a stable layout. That’s the difference between a CDN that merely hosts fonts and one that optimizes reading experience.
Frequently Asked Questions
Use swap for body and heading fonts where brand consistency matters — it guarantees the web font appears after ~100ms. Use optional for non-critical weights (e.g., 300 light, 800 extra-bold) or on performance-critical landing pages where you prefer no late swap if the font isn’t cached. Optional relies heavily on CDN and HTTP cache hits.
Preload at most 2 WOFF2 files — typically regular (400) and bold (700) for above-the-fold text. Each preload is high-priority and competes with your LCP image. Preloading more than 2 often harms performance. Let the browser fetch remaining weights normally via font-display: swap.
Browsers require CORS for fonts. Your CDN must return Access-Control-Allow-Origin: * (or your exact origin) and serve fonts with crossorigin requests. Ensure your <link rel="preload" as="font" crossorigin> includes the crossorigin attribute and that the CDN forwards the Origin header and caches separate CORS variants.
Yes, for performance. Self-hosting eliminates an extra DNS/TCP/TLS connection to fonts.googleapis.com, gives you full control over Cache-Control: immutable and Brotli, and allows aggressive subsetting and unicode-range. Google Fonts is convenient, but self-hosted WOFF2 on your own CDN edge typically saves 100-300ms on first visit.