How to Shrink Gigantic Web Fonts to Kilobytes: Subsetting & WOFF2 Tricks

This article explains why Chinese web fonts can be huge, compares TTF and WOFF2 sizes, and provides practical steps—including font subsetting with Python fontTools and on‑the‑fly loading via the FontFace API—to reduce font files from megabytes to a few kilobytes and dramatically speed up page rendering.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
How to Shrink Gigantic Web Fonts to Kilobytes: Subsetting & WOFF2 Tricks

Story Background

The original poster discovered that a poster editor was loading six fonts, the largest exceeding 20 MB and taking nearly 20 seconds to download, severely hurting user experience.

Why Are Font Files So Large?

Two main reasons for large Chinese font files:

Huge character set : English uses 26 letters plus symbols, while Chinese includes over 70,000 characters.

Complex glyph structures : Each Chinese character stores hundreds of vector points, far more than simple Latin letters.

To shrink fonts you can reduce the character set, e.g., keep only the 1,000 most common characters.

Common Web Font Formats

Typical sizes for the same font:

TTF: 16.9 MB

WOFF2: 7.4 MB (≈ 60 % compression)

WOFF2 adds compression and web‑specific metadata, allowing incremental decoding so text can appear before the whole file finishes loading.

Can TTF Be Optimized?

Back to the Core Issue

Fonts are usually referenced in three ways:

Absolute path in the project, which inflates the bundle size.

@font-face {
  font-family: 'xxx';
  src: url('../../assets/fonts.woff2');
}

CDN‑hosted font to keep the build small.

@font-face {
  font-family: 'xxx';
  src: url('https://xxx.woff2');
}

Using the FontFace constructor in JavaScript for fine‑grained control.

All three ultimately trigger a network request for the font file; the large size is the bottleneck.

Determining the Solution

Two effective approaches:

Font subsetting : Extract only the needed characters, reducing the file size dramatically. This is the foundation for any optimization.

On‑demand loading : Use the unicode-range CSS property so the browser loads only the subset needed for the characters present on the page. This requires the font to be subset first.

In this project we abandon CDN and dynamically subset local fonts on the server.

Dynamic Subsetting with Python

We use the fontTools library to create a Flask endpoint that receives the desired characters, subsets the TTF, and returns it as WOFF2.

@app.route('/font/<font_name>', methods=['GET'])
def get_font_subset(font_name):
    font_path = os.path.join(FONTS_DIR, f"{font_name}.ttf")
    chars = request.args.get('text', '')
    format = request.args.get('format', 'woff2').lower()
    unique_chars = ''.join(sorted(set(chars)))
    try:
        options = Options()
        options.flavor = format if format in {'woff', 'woff2'} else None
        options.desubroutinize = True
        subsetter = Subsetter(options=options)
        font = TTFont(font_path)
        subsetter.populate(text=unique_chars)
        subsetter.subset(font)
        buffer = io.BytesIO()
        font.save(buffer)
        buffer.seek(0)
        mime_type = {'woff2': 'font/woff2', 'woff': 'font/woff'}[format]
        response = Response(buffer.read(), mimetype=mime_type)
        return response
    except Exception as e:
        pass

On the front end we replace the original font URL with the new subset endpoint and load it via the FontFace API.

// ...other logic
Toast.loading('字体加载中');
[...new Set(fontFamilies)].forEach((fontName) => {
  const obj = fontLibrary.find(el => el?.value === fontName) ?? {};
  if (obj.value && obj.src) {
    const text = textMap[obj.value].join('');
    const font = new FontFace(
      obj.value,
      `url(http://127.0.0.1:5000/font/${obj.value}?text=${text}&format=woff2)`
    );
    font.load();
    document.fonts.add(font);
  }
});
return document.fonts.ready.finally(() => Toast.destory());

After the changes, the original 22.4 MB font shrank to just 3.6 KB, and poster generation time dropped from over 20 seconds to under 300 ms.

Conclusion

Many strategies exist for optimizing font loading; selecting the right one depends on your business scenario. Font subsetting is a highly effective technique, and combining it with on‑demand loading yields the best performance gains.

Pythonfrontend performanceWeb fontsFont SubsettingfontToolsWOFF2
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.