Web Font Optimization Guide: Balancing Design and Performance

Web fonts enhance brand identity but font files are often large. Chinese fonts can be 5-10MB. Unoptimized fonts cause FOIT or FOUT, impacting Core Web Vitals LCP by 15-20%.

Here is a concrete example: an English site loading two weights (400 and 700) plus italics typically ships more than 1MB of font files; a Chinese site is far worse because CJK fonts contain tens of thousands of glyphs — a single Regular weight of Source Han Sans in woff2 is already 4-5MB. Hanging these files on the page unprocessed severely slows first paint. Every strategy below serves the same goal: get the fonts you need rendered as soon as possible and never download a glyph you do not use.

1. Font Display Strategies

Value Behavior Best For
auto Browser default (FOIT) General
block Brief blocking (~3s) Critical brand fonts
swap Show fallback, swap when loaded Most cases (recommended)
fallback Brief block, then permanent fallback if timeout Balance
optional Very short block, cancel if timeout Maximum performance
@font-face { font-family: 'MyFont'; src: url('/fonts/myfont.woff2') format('woff2'); font-display: swap; }

Font Formats Compared

Format Compression Browser Support Notes
WOFF2 Best All modern browsers Preferred format
WOFF Good Broad support Compatible with older browsers
TTF/OTF None Everywhere Not recommended for the web
EOT Fair IE only Legacy

The size differences are dramatic in practice. Taking Source Han Sans Simplified Chinese Regular as an example: the raw TTF is around 16MB, WOFF drops it to about 10MB, and WOFF2 to roughly 4-5MB. Subset the same font to common Han characters and the woff2 can fall to a few hundred KB. That is why "WOFF2 plus subsetting" has become the standard combo for CJK font optimization.

2. Optimization Strategies

Font Subsetting (Critical for Chinese)

Remove unused characters. Chinese font files typically contain 20,000+ glyphs but a site actually uses fewer than 2,000. Subsetting removes the rest and shrinks the file by more than 90%. Besides glyphhanger, the open-source pyftsubset from fonttools does the same job:

pyftsubset SourceHanSansSC-Regular.otf \
  --text-file=chars.txt \
  --output-file=subset.woff2 \
  --flavor=woff2 \
  --layout-features='*'

chars.txt is the list of characters actually used on the page, which you can extract from your build output. For pure Chinese content, keeping 3,000-5,000 characters covers the vast majority of pages; combined with unicode-range segmentation, the browser downloads only the slices containing the text it needs.

Preload Critical Fonts

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

Self-Host vs Third-Party

Self-hosting removes DNS lookups and extra connections to a third-party origin, which matters on slow or high-latency networks. If you do keep a third-party font service, at least warm up the connection so the browser can start the request earlier:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

Self-hosting costs a little more maintenance but gives you full control over caching, font-display, and which weights actually ship — a big win on high-latency networks.

3. Best Practices

  • FOUT is better than FOIT (users prefer system fonts over blank space)
  • Limit font weights (each weight = extra download)
  • Use variable fonts (one file, multiple weights)
  • Long cache duration (1 year+) for font files
  • Use WOFF2 format (best compression, widely supported)

5. Common Font Loading Questions

Why does the page still use system fonts after loading? The most common cause is font-display: swap timing out or the font file failing to download — check the Network panel for a 200 on the font request and confirm the src path in @font-face matches your deploy directory.

Why is a Chinese site's first paint so slow? CJK fonts are several MB, and the problem is almost always "loading the whole package". Subset first, then split delivery with unicode-range so first paint only downloads the slices covering headings and buttons — typically dropping 5MB to under 500KB.

How do I verify a font actually loaded? Open Chrome DevTools → Network and filter by Font to see each file's size, duration, and priority; record the page load in the Performance panel to see exactly how long fonts block first paint.

Reference: MDN font-display https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display; Google web.dev font best practices https://web.dev/articles/font-best-practices; fonttools pyftsubset https://fonttools.readthedocs.io/en/latest/subset/index.html

6. Summary

Start with basics: WOFF2, font-display: swap, preload critical fonts. For Chinese sites, subsetting is essential.