# Serving fonts

URL: /docs/plugins/payload-fonts/serving

How the fonts you pick reach the browser: the baked next/font path that ships your site, the PreviewFonts escape hatch for previewing a live selection, plus every env var and prop that tunes them.

The active fonts apply as the `--font-set{Sans,Serif,Mono,Display}` CSS variables, so your app just
uses `font-family: var(--font-setSans)`.

There's **one main path** — baked `next/font`, the same in dev and production — and **one escape
hatch**, `<PreviewFonts>`, for the narrow case where you want the live selection without a rebuild.
This page covers both, the env vars the download reads, and the `<PreviewFonts>` props.

## The main path: baked `next/font`

This is how you ship fonts, and it's identical in development and production. A build step resolves
the `fontSet` selection and writes the fonts to disk; your layout puts them on `<html>`.

- `payload fonts:download` reads the active families straight from the database through Payload's
  Local API and writes `public/fonts/*.woff2` + `src/app/definition.ts` (a `next/font/local` module).
  Nothing needs to be running, and there's no URL or bearer token to configure — it boots your own
  Payload config. Fonts on cloud storage work the same way: the read goes through your collection's
  storage adapter.
- `extractFonts` puts the generated classes (which define the `--font-set*` variables) on `<html>`.

```tsx
import definitionFonts from '@/app/definition'
import { extractFonts } from '@pro-laico/payload-fonts'

<html className={extractFonts(definitionFonts)}>   {/* next/font: self-hosted, preloaded, no layout shift */}
  {/* … */}
</html>
```

Wire the download as **`predev` and `prebuild`** so `definition.ts` exists (and is current) before
either server starts. This is the whole story in a real site — service-co runs exactly this:

```json
// package.json — no extra env; it boots your Payload config
{
  "scripts": {
    "predev": "payload fonts:download",
    "prebuild": "payload fonts:download"
  }
}
```

`next/font/local` gives precise preloading, size-adjusted fallbacks, and content-hashed static
assets — no third-party CDN and no layout shift. The tradeoff is that a font change is baked: it
needs a rebuild (re-run the download, rebuild) to publish. To automate that, trigger a redeploy from
a `globals.fontSet.hooks.afterChange` deploy hook.

`definition.ts` and `public/fonts/` are generated, so gitignore them. `predev` regenerates them on
every dev boot, so there's no file to hand-create.

> **Alternative to `predev`:** commit an empty `definition.ts` as a baseline and skip `predev`
> entirely. The download still overwrites it on `prebuild`. (The fonts-sandbox example skips the
> baked path altogether and previews live — see below.)

> **Failures empty the definition.** Any error during the download (an unreachable database, no fonts
> chosen yet, an unexpected throw) resets `definition.ts` to an **empty** module so the build still
> compiles, rather than leaving a stale one that imports font files no longer on disk (a fresh
> checkout gitignores `public/fonts/`).

### Building where the database isn't reachable

For a build box that genuinely **can't reach the database**, the `payload-fonts-download` CLI does
the same job over HTTP: it fetches `GET /api/fonts/export` — gated by `PAYLOAD_SECRET` (Bearer) —
from a running site named by `FONT_DOWNLOAD_URL`. That instance must be up while the script runs; if
it isn't, the CLI writes an empty definition and the build ships without fonts.

`payload fonts:download` (Local API) is the recommended path; reach for the HTTP CLI only when the
build has no database. It reads several env vars — two required. See
[Environment variables](#environment-variables).

## `<PreviewFonts>`: the live escape hatch

`<PreviewFonts>` reads the **live** `fontSet` selection from the database on every render and inlines
the matching `@font-face` + `--font-set*` variables. It works in dev **and** production — it isn't a
dev-only component and it doesn't defer to the baked path; wherever you mount it, it serves the live
selection. Seeding or editing a font shows up on refresh with no build step.

Use it in the **one context where you're previewing a font** — a preview route, a `draftMode()`
branch, a font playground (the fonts-sandbox example) — **instead of** the baked `extractFonts()`
className. It does its own thing wherever you mount it and stays out of the main render path; it
doesn't replace the baked path for the rest of your site.

```tsx
import config from '@payload-config'
import { getPayload } from 'payload'
import { Suspense } from 'react'
import { PreviewFonts } from '@pro-laico/payload-fonts/PreviewFonts'

<html lang="en">
  <head>
    <Suspense fallback={null}>
      <PreviewFonts payload={getPayload({ config })} />
    </Suspense>
  </head>
  {/* … */}
</html>
```

Because it reads per render, render it inside `<Suspense>` — a **dynamic hole** — and enable
`cacheComponents` in `next.config` so the rest of the page still prerenders around it. Don't mark the
whole route dynamic — the Suspense boundary is all it needs.

`<PreviewFonts>` is a server component. Pass it a Payload instance via the `payload` prop, e.g.
`payload={getPayload({ config })}`.

## `PreviewFonts` props

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `payload` | `Payload \| Promise<Payload>` |  | The Payload instance (or its promise), e.g. getPayload({ config }). _(required)_ |
| `cssVarPrefix` | `string` | `'--font-set'` | CSS family-variable prefix; must match the download CLI cssVariablePrefix so a preview and the baked path emit the same variable names. |
| `fontSetSlug` | `string` | `'fontSet'` | Slug of the font-selection global. Auto-discovered from the plugin marker; an explicit prop wins. |
| `optimizedSlug` | `string` | `'fontOptimized'` | Slug of the optimized (served) upload collection. Auto-discovered from the plugin marker; an explicit prop wins. |
| `families` | `FontFamilyConfig[]` | `auto-discovered` | Optional. The slots are auto-discovered from the fontSet global, so you only need this to match custom per-family fallback stacks in the preview. |

> **`cssVarPrefix` and `cssVariablePrefix` are the same setting under two names — not a typo.** The
> `<PreviewFonts cssVarPrefix>` prop and the download CLI's `cssVariablePrefix` (env var
> `PAYLOAD_FONTS_CSS_VAR_PREFIX`, default `--font-set`) both set the prefix for the emitted CSS family
> variables. Keep them equal, or a preview and the baked path emit different variable names and your
> `font-family: var(--font-setSans)` rules resolve on only one of them.

## Environment variables

The download reads these env vars — full table in [Reference](/docs/plugins/payload-fonts/reference#environment-variables). The two required by the HTTP CLI: `FONT_DOWNLOAD_URL`, `PAYLOAD_SECRET`. (`payload fonts:download` boots your config, so it only needs the env your Payload app already has.)
