Jekyll Mermaid Prebuild
Pre-render Mermaid diagrams to SVG at Jekyll build time, eliminating the need for client-side JavaScript.
Why?
The mermaid.js library is ~2MB minified. This plugin renders your diagrams to static SVG files during the Jekyll build, so your visitors don't need to download and execute any JavaScript.
Features
- Converts mermaid code blocks to SVG files at build time
- Supports both backtick (
```) and tilde (~~~) fenced code blocks - Intelligent caching - only regenerates changed diagrams
- Clickable diagrams - link to full-size SVG for complex diagrams
- Configurable output directory
Requirements
- Ruby >= 3.1.0
- Jekyll >= 4.0
-
mermaid-cli (
mmdc)
Installing mermaid-cli
npm install -g @mermaid-js/mermaid-cliPuppeteer Dependencies (Linux/WSL)
The mermaid CLI uses Puppeteer (headless Chrome). On Debian/Ubuntu/WSL, install the required libraries:
sudo apt-get update
sudo apt-get install -y libgbm1 libasound2 libatk1.0-0 \
libatk-bridge2.0-0 libcups2 libdrm2 libxcomposite1 \
libxdamage1 libxfixes3 libxrandr2 libxkbcommon0 \
libpango-1.0-0 libcairo2 libnss3 libnspr4Installation
Add to your Gemfile:
group :jekyll_plugins do
gem "jekyll-mermaid-prebuild"
endRun:
bundle installUsage
Write mermaid diagrams in your markdown using fenced code blocks:
```mermaid
flowchart LR
A[Start] --> B{Decision}
B -->|Yes| C[OK]
B -->|No| D[Cancel]
```The plugin will automatically convert these to SVG files at build time.
Output
The mermaid code block is replaced with:
<figure class="mermaid-diagram">
<a href="/assets/svg/abc12345.svg">
<img src="/assets/svg/abc12345.svg" alt="Mermaid Diagram">
</a>
</figure>The image is wrapped in a link to itself, allowing users to click for a full-size view of complex diagrams.
Configuration
Add to your _config.yml:
mermaid_prebuild:
enabled: true # default: true
output_dir: assets/svg # default: assets/svg
prefers-color-scheme:
mode: light # light (default) | dark | auto — see [Color scheme](#color-scheme-mermaid-theme)
# Optional: override mmdc’s root SVG background (defaults: light=white, dark=black)
# background-color:
# light: white
# dark: black
postprocessing:
text_centering: true # default: true
overflow_protection: true # default: true
edge_label_padding: 0 # default: 0 (off); try 4-8 if needed
emoji_width_compensation: # optional, see below
flowchart: trueOptions
| Option | Default | Description |
|---|---|---|
enabled |
true |
Enable/disable the plugin |
output_dir |
assets/svg |
Directory for generated SVG files |
prefers-color-scheme |
see below |
Hash with mode (light / dark / auto) and optional background-color for chart backgrounds. See Color scheme. |
postprocessing group
All cross-browser rendering fixes live under the postprocessing: key. Each can be toggled independently.
| Option | Default | Description |
|---|---|---|
text_centering |
true |
Inject CSS to center <foreignObject> label text. Set false to disable. See Cross-browser text rendering fixes. |
overflow_protection |
true |
Inject overflow: visible on <foreignObject> elements to prevent clipping. Set false to disable. See Cross-browser text rendering fixes. |
edge_label_padding |
0 |
Extra SVG user units added to edge-label <foreignObject> widths after mmdc (off when 0, false, or omitted). Applies to all diagram types. See Cross-browser text rendering fixes. |
emoji_width_compensation |
{} |
Map of diagram types to booleans; see Emoji width compensation below. |
Color scheme (Mermaid theme)
Under mermaid_prebuild, the prefers-color-scheme key (hyphenated, like the CSS media feature) must be a YAML mapping. Use mode for the Mermaid theme / HTML behavior. Optionally nest background-color (same spelling as in CSS) with light / dark slots for each chart variant’s root SVG fill (mmdc always emits background-color: white on the root <svg>; the plugin rewrites that per file).
mermaid_prebuild:
prefers-color-scheme:
mode: auto
background-color:
light: white # default if omitted
dark: black # default if omittedmode |
Behavior |
|---|---|
light |
One SVG per diagram using Mermaid’s default (light) theme. |
dark |
One SVG per diagram using mmdc’s dark theme (mmdc -t dark). |
auto |
Two SVGs per diagram: {digest}.svg (light) and {digest}-dark.svg (dark). The embedded HTML uses two links with a small inline stylesheet so only the variant matching the visitor’s prefers-color-scheme is shown. Build cost: each diagram runs mmdc twice until both files are cached. |
Chart backgrounds: Values are injected verbatim into the root <svg style="..."> as the CSS token after background-color: (for example white, black, #fff0aa, rgb(0,0,0), transparent). They are validated conservatively: values containing quotes, angle brackets, backticks, semicolons, backslashes, or control characters are rejected and the default for that slot is used, with a warning. Keep values short (max 256 characters). Do not put raw double quotes or HTML in these strings.
Transparency: The standard CSS keyword transparent is supported and is the usual choice when you want that variant’s chart to show the page behind it (for example dark: transparent on a dark-themed site). Fully transparent RGBA such as rgba(0, 0, 0, 0) is also valid if you prefer explicit alpha.
If prefers-color-scheme is not a hash (for example a bare string), the plugin uses mode: light and default backgrounds and logs a warning. Unknown mode values fall back to light with a warning. Omitting mode is treated as light.
Cross-browser text rendering fixes
When mmdc renders a diagram, headless Chromium measures text with getBoundingClientRect() and sets each <foreignObject> to exactly that width. If the viewing browser (different OS, different fonts) renders the same text at a different width, labels can clip or shift. The plugin can apply some fixes to every generated SVG (all configurable under postprocessing:):
-
Text centering (
text_centering: true, default on): Mermaid's CSS setstext-align: centeron SVG<g>elements, but that has no effect on HTML inside<foreignObject>. The plugin injects a CSS rule (foreignObject > div { display: block !important; text-align: center }) so that label text centers within its container regardless of font metric differences. This is idempotent - if upstream Mermaid fixes this, the rule becomes redundant but harmless. -
Overflow protection (
overflow_protection: true, default on): SVG<foreignObject>elements default tooverflow: hidden, which silently clips any text that renders wider than the container. Since different build environments can produce foreignObject widths that differ by 7–22% (ish) for identical Mermaid source, the plugin injectsforeignObject { overflow: visible }so that labels are never truncated regardless of the magnitude of measurement mismatch. This covers both node and edge labels across all diagram types. This could result in labels extending beyond the bounds of their container if there was no natural padding; most cases have a few pixels of padding to spare and so this will just seamlessly fix things. -
Edge label padding (opt-in via
edge_label_padding): A fixed-pixel widening of edge label<foreignObject>elements across all diagram types. Edge labels have their own background rectangles, sooverflow: visiblealone can cause text to spill outside the background. Padding widens the container so the background matches the visible text.-
When to enable: If your CI builds produce narrower edge labels than the viewing browser expects. Complements
overflow_protectionfor edge labels specifically. -
Starting value: Try
4-8(SVG user units); increase only if needed. - Caching: The cache key includes this padding, so changing the value invalidates all cached SVGs.
-
When to enable: If your CI builds produce narrower edge labels than the viewing browser expects. Complements
Emoji width compensation
Headless Chromium (used by mermaid-cli/mmdc) undermeasures emoji glyph widths on non-Mac platforms. That can make node labels containing emoji clip in the generated SVG. This option tells the plugin to append invisible padding to emoji-containing node labels before passing the source to mmdc, so Puppeteer allocates correct widths.
This is a monkeypatch for an upstream headless Chromium bug, not a general-purpose fix. It works within specific constraints; if upstream Chromium or Mermaid fix the emoji width measurement, disable this feature.
- When to enable: Only if you see emoji text clipping in mmdc-rendered SVGs on your build platform (typically Linux/Windows).
- When not to enable: Mac build environments (emoji measure correctly there), or if upstream Chromium/mermaid fixes the issue - extra padding would then over-widen nodes.
-
Why it's in the plugin: Adding
manually in your Mermaid source would break GitHub preview, IDE preview, mermaid.live, and client-side mermaid.js, because those contexts don't have the headless-Chrome bug. The plugin injects padding only for the mmdc path, so your source stays clean everywhere.
Requirements for emoji compensation to work
The plugin uses regex to find node labels in Mermaid source. This means your source must follow these conventions for compensation to apply:
-
Use double-quoted labels on nodes with emoji:
A["🔧 Code"], notA[🔧 Code] -
Use
<br>for line breaks inside labels (variants<br/>and<br />also work) -
Flowchart only -
flowchartandgraphdiagrams. Other diagram types are not supported for automatic compensation.
Labels that don't match these patterns pass through unmodified - the plugin won't break your diagrams, it just won't compensate them.
Multi-line label behavior
For labels with <br> line breaks, the plugin only pads the longest line (if it contains emoji). Shorter lines center naturally within the container sized by the longest line. If the longest line has no emoji, no padding is applied - Puppeteer measures non-emoji text correctly, so the container is already the right size.
Fallback: manual
If you have a label that falls outside the supported patterns (e.g. Mermaid markdown strings with backtick delimiters), you can manually add entities to the label in your Mermaid source. Note that manual will render as visible trailing space in non-mmdc contexts (GitHub preview, mermaid.live, etc.).
Example config
mermaid_prebuild:
postprocessing:
emoji_width_compensation:
flowchart: trueCaching
Generated SVGs are cached in .jekyll-cache/jekyll-mermaid-prebuild/. The cache key is based on the diagram content and all output-affecting postprocessing config, so:
- Unchanged diagrams are served from cache (fast rebuilds)
- Modified diagrams are automatically regenerated
- Different diagrams with different content get different cache keys
- Enabling or disabling emoji width compensation for a diagram type invalidates cache for that content (keys include compensated source when applicable)
- Changing
edge_label_padding,text_centering,overflow_protection,prefers-color-schememode, or chart background colors invalidates cache keys
To clear the cache:
rm -rf .jekyll-cache/jekyll-mermaid-prebuild/Troubleshooting
"mmdc not found"
Install the mermaid CLI:
npm install -g @mermaid-js/mermaid-cliVerify installation:
mmdc --version"Puppeteer cannot launch headless Chrome"
Install the required system libraries (see Puppeteer Dependencies above).
Diagrams not converting
- Check build output for
MermaidPrebuild:messages - Verify mmdc works:
mmdc -i test.mmd -o test.svg - Clear cache:
rm -rf .jekyll-cache/jekyll-mermaid-prebuild/
Development
Setup
git clone https://github.com/Texarkanine/jekyll-mermaid-prebuild.git
cd jekyll-mermaid-prebuild
bundle installTesting
bundle exec rspecCode Quality
bundle exec rubocopContributing
See CONTRIBUTING.md for development guidelines.