Fonts
Typst resolves a glyph against the faces a build can see. By default those are its own bundled ones and whatever is installed on the machine, which makes the output depend on the machine: a page typeset in a face you have installed renders in a fallback on CI, out of a build that reported success.
Ship the faces with the site instead:
typst {
fonts {
paths "assets/fonts"
system #false
}
}
| Key | Default | Does |
|---|---|---|
paths |
none | Directories scanned recursively, relative to the project root. |
system |
on | Also use the fonts installed on the machine. |
Search order runs from most specific to least: Typst’s bundled faces, then your paths, then the system’s. A face you ship therefore wins over a same-named one that happens to be installed, which is the point of shipping it.
system #false is what makes the build reproducible. With it off, a page can only resolve to a bundled face or one in the project, so the same commit typesets the same bytes on your laptop and on CI.
NOTE
A directory that is not there is an error before anything compiles. Scanning one yields no faces and says nothing, so a typo would otherwise show up only as a page silently typeset in a fallback.The faces under paths are build inputs, so replacing one rebuilds the site: a new cut of a face reaches the pages, the social cards and the PDFs together. The contents are what count, not the timestamps, so a fresh checkout on CI is not a cold build. A site that names no directory pays nothing for this.
A face is used by naming it in Typst, as usual. The config only decides what can be found:
#set text(font: "Inter")
WARN
Putting fonts underassets/ means the pipeline also copies them to the output and a browser can fetch them, which is what you want for a webfont. A face you only typeset with (a PDF sidecar, a social card) is better kept outside the asset tree, so it is not published.