Atomic Payload
Examples

fonts-only

Minimal example: @pro-laico/fonts in isolation, served with next/font/local.

A minimal Atomic Payload template that exercises @pro-laico/fonts on its own. Build typefaces in the Font collection, choose the active four in the fontSet global, run the font download step, and the site serves them with next/font/local.

What it shows

The only @pro-laico dependencies are @pro-laico/fonts and @pro-laico/core, so you see the fonts plugin in isolation, set up exactly as the fonts plugin's Standalone guide describes:

  • fontsPlugin({ includeFontSet: true, fontOriginalOptions, fontOptimizedOptions }) registers the visible Font typeface collection (plus the hidden fontOriginal raw archive and fontOptimized served WOFF2s) and the fontSet global, the active sans / serif / mono / display selection (the font analog of an active icon set).
  • POST /api/seed uploads four bundled open-source fonts into fontOriginal, creates a one-weight font typeface for each (the save hook subsets each into a served fontOptimized), and points the fontSet global at the typefaces. It is auth-gated and idempotent, run from the admin dashboard, so you have fonts to pick without finding your own.
  • pnpm generate:fonts (wired as prebuild) reads the active selection from a running site, downloads the fonts to public/fonts, and writes src/app/definition.ts for next/font/local.
  • The layout applies those next/font variables to <html> and maps each role to --font-<role>. / renders a specimen per active role (pangram, size ramp, glyphs) via var(--font-<role>).

Scaffold it

npx @pro-laico/create-atomic-payload my-fonts --template fonts-only
pnpm dlx @pro-laico/create-atomic-payload my-fonts --template fonts-only
yarn dlx @pro-laico/create-atomic-payload my-fonts --template fonts-only

Then set a long PAYLOAD_SECRET in .env, generate types and the import map, and start dev:

pnpm generate:types        # generates src/payload-types.ts + augment
pnpm generate:importmap    # populates src/app/(payload)/admin/importMap.js
pnpm dev

Ships with SQLite (@payloadcms/db-sqlite) at ./font-only.db, with no DB server. Swap adapters by editing src/payload.config.ts.

The flow:

  1. Visit /, an explainer page. Logged out, it points you to the admin.
  2. Open /admin, create your first user.
  3. On the dashboard, click Seed sample fonts (or upload your own and pick them in the fontSet global).
  4. Run pnpm setup:fonts to download the active fonts for next/font/local. It boots a temporary server, runs the download against it (authenticating with your PAYLOAD_SECRET), and shuts it down.
  5. Run pnpm dev again and open / to see the four active fonts rendered as specimens.

How it works

build a Font typeface (admin)  ──►  raw file → fontOriginal (private)
        │                            save hook subsets it → fontOptimized (served WOFF2)
seed: POST /api/seed uploads public/seed-fonts/*.woff2 → fontOriginal,
      creates a font typeface per role, then
      updateGlobal('fontSet', { sans, serif, mono, display })  ◄── active set

pnpm generate:fonts ── GET /api/fonts/export @ FONT_DOWNLOAD_URL ──► public/fonts/* + src/app/definition.ts
        │   (export reads each active typeface's fontOptimized bytes)
layout.tsx → applies the next/font variables from definition.ts to <html>,
             maps --font-<role>: var(--font-set<Role>)

page.tsx → getActiveFonts() [fontSet global] ──► a specimen per role via var(--font-<role>)
route is force-dynamic ──► seeding / swapping the active set shows up on the next render

The active selection lives in a global because it's the active-selection layer, mirroring icons-only's active iconSet. The Font collection can hold many typefaces; the fontSet global picks the one sans / serif / mono / display the site uses, editable at /admin/globals/fontSet.

The download step

pnpm build runs prebuildgenerate:fonts, which calls the fonts plugin's export endpoint on a reachable Payload instance and writes the fonts to disk for next/font/local. It authenticates with PAYLOAD_SECRET and fetches from FONT_DOWNLOAD_URL, the running instance to pull the active fonts from (see .env.example):

FONT_DOWNLOAD_URL=http://localhost:3000   # the running instance to fetch from
PAYLOAD_SECRET=...                        # authenticates the request

The endpoint reads each active typeface's served (fontOptimized) font server-side, from disk for local storage, or by fetching the URL Payload reports for cloud adapters (Vercel Blob, S3, and the like), so there's no storage token or login to manage. When neither the URL nor the secret resolves, the download is skipped (the build still succeeds) and the specimens fall back to system fonts until you run it.

Local dev, one command. generate:fonts needs a running Payload to fetch from, which is awkward before anything's deployed. pnpm setup:fonts handles it: it boots a temporary next dev server, points FONT_DOWNLOAD_URL at it, runs the download, then shuts the server down. No credentials needed: it authenticates with your existing PAYLOAD_SECRET.

What to look at

FileWhat's there
src/payload.config.tsbuildConfig: fontsPlugin({ includeFontSet: true }) with fontOriginalOptions / fontOptimizedOptions staticDirs
src/lib/fontDir.tsFONT_STATIC_DIR (shared with the config, no @payload-config import)
src/lib/fonts.tsgetActiveFonts(): reads the active selection from the fontSet global
src/seed/sampleFonts.tsmanifest mapping bundled files → title + role
src/components/admin/BeforeDashboard + SeedControls (seed/reset on the dashboard)
src/app/definition.tsgenerated by generate:fonts (gitignored): the next/font/local declarations
src/app/(frontend)/layout.tsxapplies the next/font variables + maps --font-<role>
src/app/(frontend)/page.tsxexplainer + a specimen per active role + "use them together"
src/app/(payload)/api/seed/route.tsPOST /api/seed → upload originals, create typefaces, set the fontSet global
src/app/(payload)/api/reset/route.tsPOST /api/reset → clear the global + delete typefaces (cascades to originals/optimized)
public/seed-fonts/*.woff2bundled OFL fonts (committed) + LICENSES.md

FONT_STATIC_DIR (./media, gitignored) is where Payload writes uploads — the raw archive under original/, the served WOFF2s under optimized/ — shared via src/lib/fontDir.ts so the config and the export read path never drift. The bundled seed fonts (Inter, Lora, JetBrains Mono, Abril Fatface) are OFL: see public/seed-fonts/LICENSES.md, and replace them before shipping a real project.

On this page