Web Font Implementation Guide

Complete end-to-end checklist for deploying fonts on a production website. From selecting a font license to verifying Core Web Vitals, every step in order.

Phase 1 — Selection and licensing

  1. Choose your fonts — one heading font, one body font maximum. Prefer variable fonts when available.
  2. Verify the license — use the License Metadata Viewer to read the embedded license. Confirm commercial use, web embedding, and modification (subsetting) rights.
  3. Check language coverage — use the Language Support Checker if your site serves non-English content.
  4. Test rendering — use the Font Preview Generator to confirm the font renders your actual content correctly.

Phase 2 — File preparation

  1. Download source files — TTF or OTF from the font foundry.
  2. Subset if needed — use pyftsubset to strip unused scripts. See the Optimization Guide.
  3. Convert to WOFF2 — use the TTF to WOFF2 Converter or OTF to WOFF2 Converter.
  4. Verify output — use the Performance Checker to confirm format and size.
  5. Place in project — put WOFF2 files in /public/fonts/ or equivalent.

Phase 3 — CSS and HTML integration

  1. Generate @font-face CSS — use the @font-face Generator. Copy the output into fonts.css.
  2. Generate fallback stack — use the Fallback Generator. Apply to body.
  3. Generate preload tag — use the Preload Generator. Add the tag to <head>.
  4. Verify integration — use the Integration Tester in a staging environment to confirm the font loads.
<head>
  <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="stylesheet" href="/css/fonts.css">
  <link rel="stylesheet" href="/css/main.css">
</head>
/* fonts.css */
@font-face {
  font-family: "Main";
  src: url("/fonts/main.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
/* main.css */
body {
  font-family: "Main", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

Phase 4 — Server configuration

  1. Set long cache headers for font files (one year, immutable).
  2. Set correct MIME types — font/woff2 for .woff2 files. Most servers get this wrong by default.
  3. Enable CORS for fonts if serving from a CDN or subdomain — Access-Control-Allow-Origin: *.
  4. Enable Brotli compression — serves .woff2 as-is (already compressed), but ensures your HTML/CSS/JS gets Brotli too.

Cloudflare Pages _headers example:

/fonts/*
  Cache-Control: public, max-age=31536000, immutable
  Access-Control-Allow-Origin: *
  Content-Type: font/woff2

Phase 5 — Verification

  1. Check Core Web Vitals in Google Search Console after 28 days of real-user data. The report shows LCP, INP, and CLS at the 75th percentile.
  2. Test in real browsers — Chrome, Firefox, Safari, Edge. Use the Integration Tester on each.
  3. Test on mobile networks — throttle to Slow 3G in DevTools. Confirm fallback text is visible within 1s.
  4. Test with disabled cache — verify the font loads on a fresh visit.
  5. Test with fonts blocked — verify the fallback stack prevents invisible text.

Phase 6 — Ongoing maintenance

  • Monitor Google Search Console Core Web Vitals monthly.
  • Update fonts only when the design system changes — each update is a new cache entry.
  • Review the fallback stack when adding new languages or scripts.
  • Re-run the Performance Checker when adding new font files.
  • Re-run the Integration Tester after any server or CDN configuration change.

Common mistakes to avoid

MistakeImpactFix
Using @import for fontsBlocks rendering — worst-case 1s+ LCP delayUse <link> in <head>
Missing crossorigin on preloadDouble font downloadAdd crossorigin
Preloading 5+ fontsSteals bandwidth from LCP-critical resourcesPreload only the critical font
Serving TTF or OTF60–75% larger than WOFF2Convert to WOFF2
Wrong MIME typeSome browsers ignore the fontServe as font/woff2
No fallback familyText renders in unpredictable defaultAlways end with generic family
Loading 9 static weights~900KB for one familyUse a variable font (~100KB)

Frequently asked questions

What is the recommended web font setup for production?

Self-host WOFF2 variable font files (or subsetted static weights), serve via @font-face with font-display: swap, preload the critical font with crossorigin, include a metric-adjusted fallback, and set 1-year immutable cache headers.

Do I need to serve WOFF, WOFF2, TTF, and OTF all together?

No. WOFF2 alone is sufficient for all browsers released after 2016. Serving multiple formats doubles or triples your deployment size without meaningful compatibility benefit for modern audiences.

How many fonts should I load?

One or two families maximum. Prefer one variable font if possible. Each additional family adds network requests, delays LCP, and increases visual noise.

Should I use Google Fonts or self-host?

Self-host for production. Google Fonts CDN is fine for prototypes but adds two DNS lookups, prevents stable preload URLs, and requires GDPR consent in the EU.

What if my fonts are on a CDN?

Ensure the CDN sets Access-Control-Allow-Origin headers for font files, uses font/woff2 MIME type, and serves with long cache headers. Preload URLs must match the CDN URL exactly.

Related tools