Server-controlled font delivery for SaaS applications.
This repository demonstrates the correct pattern for serving licensed fonts in a SaaS architecture. An Express server controls all font delivery through a dedicated endpoint — fonts never enter the client bundle and are never fetched from an uncontrolled CDN.
- An Express server (
server/) that serves font files from a controlled endpoint with scoped CORS headers - A client (
client/) that loads fonts via@font-facepointing to the server endpoint - A subset
.woff2checked in underfonts/so CI succeeds without secrets (replace with your own licensed files for forks or private use) - How server-side delivery keeps font assets and licensing obligations under the operator’s control
In a SaaS product, end users interact with fonts rendered by your application — constituting font access that requires server or app licensing. Delivering fonts from your own server endpoint (rather than a public CDN or client bundle) keeps delivery within your licensed infrastructure and allows you to enforce access controls, scope delivery to known origins, and maintain audit visibility.
This pattern implements the following assertions from reference-fonts-implementation:
pc-004— web apps and SaaS products require server-level licensingpc-008— self-hosting web fonts requires a web font licensepc-009— in a self-hosted model, Monotype provides the licensing and governance layer; customer infrastructure handles deliverypc-010— cross-origin font delivery requires CORS configuration; missing headers cause silent font blockingpc-012— some Monotype web font licenses require a tracking script alongside self-hosted font files; this font endpoint handles delivery only—load any required tracking from the client app (for example a script tag inclient/index.html) when your license mandates it. For privacy-related scope, see the Clarification on pc-012.
- Obtain font files under a valid Monotype web font license (this repo ships a small subset for build/CI; use your own files in forks or production)
- Place
.woff2files infonts/and update the filename inserver/index.jsand the URL inclient/fonts.cssto match - Run the font server and client from two terminals:
# Terminal 1 — font server (default ALLOWED_ORIGIN: http://localhost:5173)
npm install
npm start
# Terminal 2 — static client server
npx serve client --listen 5173- Visit
http://localhost:5173in a browser
CI / local smoke: Run npm test after npm install. It starts the font server briefly, retries until GET /fonts/myfont succeeds, asserts Access-Control-Allow-Origin, Vary, Cache-Control, and that X-Powered-By is not sent, then stops the server (same behavior as GitHub Actions).
This two-server setup is intentional — it replicates the cross-origin scenario the pattern is designed for: client and font server on different origins, with the Access-Control-Allow-Origin header controlling which client origins may load fonts.
To test with a different client origin or in a deployed environment:
ALLOWED_ORIGIN=https://yourapp.com npm startNote: Do not open
client/index.htmldirectly as afile://URL. Browsers sendOrigin: nullfor file-based requests, which will not match the configured allowed origin and will cause the font to be silently blocked.
This repository includes fonts/MyFont.woff2, a heavily subsetted version of Gotham Regular, so GitHub Actions works out of the box. That file is licensed only for limited testing per LICENSE (Monotype terms) and this README’s License section—not for regular website use or redistribution. For your own project, replace the file and update the filename in server/index.js and the URL in client/fonts.css to match. See fonts/placeholder.txt for placement notes.
To commit a different binary despite *.woff2 in .gitignore, use git add -f fonts/YourFile.woff2 once, or add a !fonts/YourFile.woff2 line after the *.woff2 rule.
- Node.js 18+
This example demonstrates where font-delivery controls belong in a SaaS architecture. It is intentionally simplified:
- No authentication logic is shown
- No tenant binding is implemented
- CORS is scoped to a configurable origin for demonstration
In a production SaaS system, this endpoint would typically enforce:
- tenant identity
- product entitlements
- access tokens or signed URLs
- rate limits and audit logging
The demo server sets Vary: Origin alongside a reflected Access-Control-Allow-Origin so shared caches do not serve a font response with the wrong CORS to another browser origin. It also sets a short Cache-Control: private, max-age=300 as a starting point; tune caching (and CDN behavior) once entitlements and privacy requirements are clear. Add a CORS OPTIONS handler only if font requests stop being “simple” (for example if you add custom headers on authenticated font fetches).
- pattern-nextjs-webfonts — Next.js build-time font loading via
next/font/local - pattern-react-webfonts — React component library with CSS variable delivery
- pattern-cicd-fonts-usage — CI/CD pipeline font management
- pattern-variable-fonts-usage — variable font axes via CSS
Use GitHub Discussions (Q&A category) for questions about this pattern.
Sample application code in this repository is licensed under the MIT License. The subset font file in fonts/ is included only as a build/CI demonstration asset and licensed for limited testing purposes only; it is not licensed for regular use on websites or redistribution. Please refer to the LICENSE file in the repository for both licenses. Canonical assertion text in reference-fonts-implementation remains subject to that repository’s terms.