How to automate google fonts self-hosting in your build pipeline
- Step 1Write a fonts.config.json — Specify your families: { "fonts": [{ "name": "Inter", "weights": [400, 700] }] }. This config lives in source control and drives every regeneration.
- Step 2Run the fetch script — Node script reads the config, builds the Google Fonts CSS API URL, fetches the CSS with a Chrome User-Agent (so Google serves WOFF2), parses every src URL, downloads each WOFF2 to public/fonts/, and rewrites the CSS to use relative paths.
- Step 3Commit the output — Commit public/fonts/*.woff2 and the rewritten CSS. Future deploys serve them directly. Re-run the script only when the config changes — most teams refresh quarterly or on a major design refresh.
Frequently asked questions
Why a Chrome User-Agent?+
The Google Fonts CSS API serves different formats based on User-Agent. Default Node.js fetch sends an unfamiliar UA and gets TTF instead of WOFF2. Always set User-Agent to a recent Chrome to get the smallest format.
Should I use a pinned Google Fonts version?+
There's no public version pinning, but the SHA-256 of each WOFF2 changes only when Google publishes a new revision (rare for established fonts). Capture the hashes in your repo and add a CI check that verifies them — if they change unexpectedly, you'll know immediately.
Can I cache results between CI runs?+
Yes — cache public/fonts/ keyed by the SHA-256 of fonts.config.json. Re-runs without config changes skip the fetch entirely. CI cache hit time: <100 ms.
What about variable fonts in the script?+
Specify a weight range in the config (e.g., "weights": ["100..900"]) and the Google Fonts API returns the variable WOFF2. Same pipeline; smaller output. The JAD generator handles this transparently.
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.