Skip to main content
Set FONT_MIRROR_URL to serve all catalog fonts from your own static host. Current web and mobile clients, server-generated PDFs, font-picker previews, public resume HTML, and social preview cards use the same mirror. The default deployment continues to use the original font hosts. The mirror is a directory of prepared files. Rendering never downloads a missing file from Google or jsDelivr. PDF export reports missing fonts; font-picker labels and public HTML can fall back to system fonts when files cannot load. Catalog font containers are checked for required tables, valid offsets and table checksums before caching. This rejects truncated files and HTML fallbacks returned with HTTP 200; it does not certify every OpenType table or glyph. The saved resume’s typography is not changed.

Prepare the files

Use a source checkout matching the deployed release (Node 24 and its pinned pnpm). Inspect the complete catalog:
Prepare a new directory; the tool refuses to overwrite an existing directory:
The tool downloads every selected family’s preview, weights and italics, copies original license notices, and writes manifest.json with source URLs, sizes and SHA-256 checksums only after every download succeeds. Four files download concurrently; each file has a size limit and timeout. Prepare while online, then transfer the whole directory, including licenses/, to the static host. No font files are added to the application image. For a smaller installation or a test, select families and resume languages:
This includes the selected families and the renderer’s punctuation, symbol, emoji and requested-language fallbacks. A subset does not restrict the font picker: choosing another family, importing an existing resume, or adding another writing system can require additional files. Use --all for the full catalog. Include all families used by your resumes and letters, and repeat preparation after catalog changes on an upgrade. If a font’s original license notice cannot be located, preparation stops. Obtain that font’s notice from its upstream project and put it in a directory as <family>.txt, then pass --licenses <directory>. No license is inferred or substituted. A failed preparation directory is incomplete; use a new directory for the next attempt. The source host and path form the mirror path, for example:
Keep filenames and directory case unchanged. Serve bytes directly, with no authentication or redirects.

Configure the instance

This address must be reachable from both browsers/devices and the app server. A Docker service name such as http://fonts is not reachable from a visitor’s browser. Use HTTPS when the application uses HTTPS, and serve the mirror with CORS allowing font reads from the application, public/custom resume domains and sandboxed previews. Since fonts are public assets, Access-Control-Allow-Origin: * is suitable when no credentials are required. The setting accepts an absolute HTTP(S) URL with an optional path, without credentials, a query or fragment. For Docker, add the variable to the app’s environment and restart/recreate the app container. No frontend rebuild is needed. As one local-development example, expose a static nginx container on port 8080 with the prepared folder mounted read-only at /usr/share/nginx/html. Use a configuration such as:
Set FONT_MIRROR_URL to that server’s externally reachable address, for example http://192.168.1.20:8080 on a trusted local HTTP development network. For production, serve the same directory through your HTTPS static host. On Vercel, set the backend environment variable and redeploy; the mirror remains on the external static host. FONT_MIRROR_URL takes precedence over the legacy WEB_FONT_BASE_URL. Remove it and restart to return to the previous font behavior. Configuration reaches current clients through the public flags endpoint; it contains no credentials. Reload web clients after changing it. Mobile saves each server’s configuration and reuses it for five minutes; after that, it confirms the configuration before the next render, and keeps the saved one if the server can’t be reached. A device that has never read the configuration (first use, or offline right after updating the app) renders only fonts it already has and downloads none until it can read the configuration. Previously installed mobile versions do not understand this setting and must be updated. When replacing font bytes, publish a versioned mirror path (for example /resume/v2) and update the setting. Font caches key by the resolved URL; overwriting a file at the same URL does not invalidate running clients. Keep the previous mirror path available during rollout for clients still using the previous configuration.

Verify delivery

Start with cleared font/browser caches. Block requests to fonts.gstatic.com, fonts.gstatic.cn and cdn.jsdelivr.net, then open the font picker, edit a resume, export its PDF and visit its public page. Check attempted requests, including failures; only your configured mirror should receive font requests. Include bold, italics, the resume’s languages, arrows and symbols. Test a letter and a mobile client as well. Temporarily remove a required mirrored file: PDF generation should report a font error, with no retry to an original font host. Put the file back and retry. Mirror font fetches refuse redirects; public HTML’s font CSP restricts font loads to the application and mirror origins. Images and optional AI services have their own network behavior: this setting controls catalog fonts, not every external resource on a resume.

Legacy Computer Modern server mirror

Set WEB_FONT_BASE_URL when you want the Node.js server to fetch Computer Modern font files from a mirror you control. You need a running Reactive Resume instance and an HTTP(S) server that can serve static files.
This setting does not make font loading offline or Google-free. It covers only five Computer Modern families in server-generated PDFs. The other catalog families, including Noto fallback fonts, keep their Google URLs. Browser PDF rendering and font-picker previews keep all their original URLs, even for Computer Modern fonts.

1. Prepare the mirror

Copy the font/ directory from the bitmaks/cm-web-fonts source used by your release’s packages/fonts/src/webfontlist.json to your static file server. Keep the directory names, filenames and case:
These directories supply Computer Modern Bright, Concrete, Sans, Serif and Typewriter. Mirror every referenced weight and italic file, not only the preview font. The app does not bundle these files or populate your mirror. Keep the upstream license notices with your mirror. For example, with a base URL of https://fonts.example.com/cm, this original URL:
becomes:
Use an absolute HTTP(S) URL without a query string or fragment. Surrounding whitespace and trailing slashes are removed. Do not include the font/ segment in the base URL: the resolver appends it. Use an address reachable from the app server; inside Docker, localhost refers to the app container itself. The mirror must serve font bytes without a login page.

2. Configure the deployment

The server validates the URL at startup. An unset or empty value preserves the original URLs. Remove the variable and restart or redeploy to return to the default CDN. Set this value before starting the server. The server PDF entry supplies it to the font loader; the shared font catalog does not read environment variables. This setting is not exposed to the Vite browser app; rebuilding the web app does not enable browser mirroring. The Cloudflare Workers deployment is not covered by this Node.js server setup.

3. Check delivery

From the app server’s network, request a mirrored file:
Then generate a PDF through the server for a resume using Computer Modern Bright. Check the mirror’s access logs for requests to font/Bright/. Repeat with any other Computer Modern families you use. A missing weight or italic file can fail PDF rendering; the mirror is not retried against the original CDN. Server-to-server font fetches do not require CORS headers. If another browser application also uses your mirror, configure that host to allow its origin and serve the appropriate font content type. CORS headers alone do not enable this setting in Reactive Resume’s browser app. External Google requests can still occur for punctuation, symbols, emoji or language fallback fonts, even when the chosen heading and body fonts are Computer Modern. Browser network requests to the original font hosts are also expected with the current implementation.

Source-checkout smoke test

With the example multilingual subset prepared and served, run the dedicated browser test against the disposable local database. It checks the actual runtime flags, browser PDF download, server PDF export and public HTML while blocking browser requests to the original font hosts. It requires Chromium, Poppler’s pdftotext, and local PostgreSQL, as described in Development setup:
This operator test skips when FONT_MIRROR_URL is unset. Unit tests cover default and mirrored resolution, worker/main-thread handoff, native server switching, offline configuration, missing fonts and refused redirects.