How to build a static fallback pipeline for variable fonts
- Step 1Author the variable font in the source tree — Single variable WOFF2 in src/fonts/. The freezing pipeline derives every static instance from this single source — no duplicate glyph data in source control.
- Step 2Generate static instances at build — Node script runs the freezer for each named instance (Light, Regular, Bold, etc.). Output: dist/fonts/family-light.ttf, family-regular.ttf, family-bold.ttf — one per weight.
- Step 3Emit a multi-source @font-face — src list orders variable WOFF2 first (modern browsers stop here) and static WOFF2 fallbacks afterward. Browsers walk the list and pick the first format they support — variable for new, static for legacy.
Frequently asked questions
How does the browser pick variable vs static?+
Via format() hints. Variable WOFF2 declares format("woff2-variations") (or just "woff2" depending on tooling). If the browser supports variable fonts, it uses that file; otherwise it walks down to the static fallback.
Should I include the format("woff2-variations") hint?+
It's optional but cleaner. Older browsers without variable support still attempt to load files declared as woff2-variations, fail gracefully, and fall through to the next src — same outcome, more bytes wasted on the failed attempt.
What about the Italic weights?+
Most families separate Italic into a distinct family (FamilyName-Italic.var.woff2). Run the same freezing pipeline against the italic variable file to produce italic statics. The @font-face block uses font-style: italic to select.
Is there a CI cost?+
Each freeze takes ~200 ms. For a 5-weight family, the full pipeline adds about 1 second to CI. The cache hit case (variable file unchanged) skips freezing entirely — typical re-runs add zero time.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.