Self-hosting the proxy
What the proxy will and won't do, the endpoints it serves, and how to deploy your own.
The proxy is a small Nitro app in the proxy/ directory of the unifont repository. Provider APIs send no CORS headers, so it's how browsers and web containers reach them at all.
Deploy your own if you resolve fonts in a browser or a web container at runtime and you need that to keep working. Nothing else needs it: on a server, in a worker, in CI, or at build time (what @nuxt/fonts and fontless do) the provider APIs are called directly.
What it will and won't do
It's read-only, and narrow on purpose:
GETandHEADonly, and only the routes below. Anything else is a404.- Only the query parameters named per route. Everything else is dropped before the upstream call.
- Only the upstream
content-typeis carried back. No other upstream header is replayed. - No credentials, no cookies, no auth, in either direction.
- Font files never go through it. They come from the provider's own CDN, which already allows cross-origin requests.
Endpoints
GET / returns the endpoint list as JSON. Every route is versioned:
| Route | Upstream | Query |
|---|---|---|
/adobe/v1/kit/:id | typekit.com/api/v1/json/kits/:id/published | |
/adobe/v1/kit-css/:id | use.typekit.net/:id.css | |
/bunny/v1/list | fonts.bunny.net/list | |
/bunny/v1/css | fonts.bunny.net/css | family |
/fontshare/v1/fonts | api.fontshare.com/v2/fonts | offset, limit |
/fontshare/v1/css | api.fontshare.com/v2/css | f[] |
/fontsource/v1/fonts | api.fontsource.org/v1/fonts | |
/fontsource/v1/fonts/:id | api.fontsource.org/v1/fonts/:id | |
/fontsource/v1/variable/:id | api.fontsource.org/v1/variable/:id | |
/google/v1/fonts | fonts.google.com/metadata/fonts | |
/google/v1/css | fonts.googleapis.com/css2 | family, text, icon_names, format |
/google/v1/icons | fonts.google.com/metadata/icons | |
/google/v1/icon | fonts.googleapis.com/icon | family, format |
format is one of woff2 (the default), woff, ttf or eot. Each one maps to the user agent Google expects for that format.
Responses carry cache-control (public, max-age = s-maxage, plus stale-while-revalidate), an etag, and 304 support. Metadata is cached for six hours, generated CSS for a day, Adobe kits for five minutes.
Deploying it
Nothing here needs a database, credentials or environment variables.
- Vercel, Netlify, Cloudflare: create a project with
proxy/as the root directory. Nitro picks up the preset from the environment, and the build command ispnpm build. - Node:
pnpm build, then run.output/server/index.mjs.
Once it's up, GET / should return the endpoint list. Pointing apiBase at it is the only change your application needs:
const unifont = await createUnifont([providers.google()], {
apiBase: 'https://fonts.example.com',
})A fresh deployment is cold, so the first request for each endpoint waits on the upstream. The cache lives in the process, which on a serverless host means most of the work is done by the CDN in front. That's what the cache-control headers are for.