Mermaid in Hugo, Jekyll, and VuePress
Last updated: 2026-05-01
Static site generators usually render Markdown to HTML at build time. Mermaid, on the other hand, is a JavaScript library that produces SVG at runtime in the browser. To make the two work together, the SSG has to leave the Mermaid source in the page in a way the browser-side library can find later.
This guide covers Hugo, Jekyll, and VuePress — each takes a slightly different shape.
Hugo
Hugo (since v0.93) understands fenced mermaid code blocks but only emits raw HTML at build time; you still have to load the Mermaid library yourself. The recipe:
- Configure Hugo’s Markdown engine to allow raw HTML pass-through. In
config.yaml:markup: goldmark: renderer: unsafe: true - Add a render-hook for code blocks at
layouts/_default/_markup/render-codeblock-mermaid.html:<pre class="mermaid">{{- .Inner | safeHTML }}</pre> {{ .Page.Store.Set "hasMermaid" true }} - In your base template, conditionally include Mermaid only on pages that contain a diagram:
{{ if .Page.Store.Get "hasMermaid" }} <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true, securityLevel: 'strict' }); </script> {{ end }}
This keeps the Mermaid library off pages that don’t need it — important if you care about Core Web Vitals on your home page.
Jekyll
Jekyll has no built-in Mermaid awareness. Two routes work:
Route A — kramdown + a custom Liquid filter. The cleanest result, but you write a small plugin. Inside _plugins/mermaid.rb, register a tag that wraps content in <pre class="mermaid">. Pages that use the tag emit the right HTML; the rest are unchanged.
Route B — kramdown’s GFM mode + a global script. Less plumbing. Configure _config.yml to use the GFM input:
markdown: kramdown
kramdown:
input: GFM
Then at the bottom of your default layout, include the Mermaid library and tell it to scan the page:
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, securityLevel: 'strict' });
</script>
The trade-off is that Mermaid loads on every page, even ones with no diagram, costing roughly 200 KB of JavaScript. For a docs site this is usually acceptable; for a personal blog with twenty posts, prefer Route A.
VuePress
VuePress 2 has good plugin support. The community plugin @hsorby/vuepress-plugin-markdown-it-mermaid (or the older vuepress-plugin-mermaidjs) handles the integration in three lines of config.ts:
import { defineUserConfig } from 'vuepress';
import { mermaid } from '@hsorby/vuepress-plugin-markdown-it-mermaid';
export default defineUserConfig({
plugins: [mermaid()],
});
The plugin lazy-loads Mermaid only on pages that contain a fenced mermaid block, which is the right default. Themes follow VuePress’ own light/dark mode automatically.
Hosting on GitHub Pages
A GitHub Pages site built with Jekyll does not render Mermaid the same way the GitHub web UI does. The web UI runs Mermaid in JavaScript when displaying a .md file; Pages publishes static HTML produced by Jekyll, which by default has no idea what Mermaid is.
If you migrate a repo’s README into a Pages site, expect to add the Jekyll wiring above. The rendered output will look identical, but the path to get there is different.
Common pitfalls
- Smart-quote replacement — many SSG themes apply typographic substitutions that turn
"into"/". Mermaid rejects smart quotes. Make sure your code block escape rules apply. - Trailing-whitespace stripping — some pre-processors trim trailing spaces inside fenced blocks. Mermaid is mostly tolerant, but
ganttsyntax with explicit spacing breaks if your build pipeline is too aggressive. - Lazy-loading caveat — if you lazy-load Mermaid only on pages that need it, double-check your search index isn’t surfacing those pages with a broken-looking diagram in the preview snippet.
A draft-then-paste workflow
Whatever SSG you use, the development loop is fastest if you draft diagrams away from the build:
- Open our preview, iterate until the diagram looks right.
- Copy the source, paste into the Markdown.
- Build locally and verify the page renders the diagram, not the source.
When the rendering differs between the preview and the deployed page, it’s almost always a version skew between Mermaid in the preview (v11) and the version your SSG bundles. Pinning your SSG’s Mermaid version is the durable fix.