Draft: this page sits outside the guide's focus.
Nuxt ContentLesson
Mermaid pre-rendered to committed SVG
A diagram written as text stays easy to change and to review in a diff. The usual cost is paid by every reader: the page downloads a diagramming library and draws the picture in the browser. Terrarium draws each diagram once, when someone writes it, and serves a plain SVG file.
Know Mermaid already? Skip to In Terrarium.
Outside the box · General
The concept
Mermaid turns a text definition, such as a flowchart's nodes and arrows, into a diagram. On a web page it usually runs in the browser: the library finds each definition and replaces it with an SVG. Your code can also call mermaid.render(id, definition), which returns a promise of { svg }: the SVG as a string, to place or save as you like.
To lay a diagram out, Mermaid measures the drawn width of each label in a real browser. So the result depends on the font that is loaded when it measures. The usage docs warn that a web font which hasn't loaded yet leaves labels out of bounds.
Colours and fonts come from a theme. Only the base theme can be customised, through themeVariables such as primaryColor, lineColor, fontFamily and fontSize (theming docs).
Full referenceMermaid docs: About Mermaid
In Terrarium
A writer puts a mermaid code block in any 's Markdown, runs pnpm render:mermaid, and commits the SVG it writes to app/assets/mermaid/. The file is named after a hash of the diagram's source, mermaidKey in app/utils/mermaid.ts. The same source on two pages of one Tenant shares one file, and an edited diagram gets a new one. A diagram drawn in a Tenant's own font (below) adds that font to the hash; the default font adds nothing.
scripts/gate/render-mermaid.ts does the drawing. It opens Chromium, loads each Tenant's render font and waits until it is ready, then renders every diagram with the base theme. Each colour variable is first set to an unusual placeholder colour, then every placeholder in the SVG is rewritten to a CSS variable with a fallback:
16export const DIAGRAM_TOKENS: Record<string, string> = {
17 primaryColor: '--diagram-node-bg',
18 primaryBorderColor: '--diagram-node-border',
19 primaryTextColor: '--diagram-node-text',
20 lineColor: '--diagram-line',
21 edgeLabelBackground: '--diagram-edge-label-bg',
22 clusterBkg: '--diagram-cluster-bg',
23 clusterBorder: '--diagram-cluster-border',
24}
So a committed SVG takes its colours from the page it sits on. The Journal points these variables at its own design tokens in layers/journal/app/assets/theme.css, so its dark mode carries into its diagrams too.
A diagram is drawn in a font file the repo bundles, at a fixed size, and the page serves that same font under the same family name. A Tenant gets the default font unless its layer has a mermaid-font.ts, as layers/glassbox/mermaid-font.ts does. This is the default:
54export const MERMAID_RENDER_FONT: MermaidFont = {
55 face: 'Gelasio',
56 fontFamily: 'Gelasio, Georgia, serif',
57 fontSize: '20px',
58 weight: '400',
59 woff2: 'app/assets/fonts/gelasio-latin-400-normal.woff2',
60}
On the page, app/components/content/ProsePre.vue hands each mermaid block to app/components/MermaidDiagram.vue. It hashes the source the same way, bundles every committed SVG, inserts the matching one as HTML, and ships Gelasio, the default, with an @font-face rule. If no SVG matches, it shows the source as plain text. mermaid is a development dependency only, so readers never download it.
scripts/gate/validate-mermaid-svgs.ts is a step of the , the part of the that always runs. It reads files only, with no browser. It fails when a diagram has no SVG, an SVG matches no diagram or is empty, or an SVG could run code, which app/utils/svg.ts checks.
Why it's built this way
Mermaid diagrams drawn once and committed as SVG: ADR-0024 (docs/adr/0024-build-time-mermaid-prerender.md).
What to watch for
- The Gate checks that an SVG exists, not that it is current. A new source has a new hash, so a forgotten render fails. But after a change to the font, the theme or Mermaid itself, every old SVG still passes. Run
pnpm render:mermaidto redraw stale ones (--checklists them). - Only colours can be themed from CSS. Setting a font variable in a Tenant's CSS changes nothing; a Tenant's font comes from its
mermaid-font.ts. A new font means redrawing every diagram drawn in it. - A wider font on the reader's side can clip labels. If the bundled font fails to load, the browser falls back to the next font in its stack, which may be wider than the font the boxes were measured in. The renderer lets each node's label spill into its padding to soften that.
Try it in your project
Install mermaid-cli, which draws Mermaid files in a headless browser, and render one diagram to a file:
npx -p @mermaid-js/mermaid-cli mmdc -i diagram.mmd -o diagram.svg
Commit diagram.svg beside diagram.mmd, show the SVG on your page instead of loading Mermaid, and add a CI check that fails when an .mmd file has no SVG.
Where next
- LessonOverriding prose components
- PageMerge gate
- JournalArchitecture & Deployment
Last updated · Latest source on GitHub ↗