Add Mermaid Diagrams to a Hugo Blog
A while back I wrote about adding ECharts to this blog with Hugo Shortcodes. But for day-to-day technical writing, what I usually need is flowcharts and sequence diagrams — the “text-is-the-picture” kind. That’s exactly what Mermaid does: describe a diagram in a few lines of text, no canvas dragging, no pasting screenshots.
And Hugo now has a cleaner approach than Shortcodes — the code block render hook. Since Hugo v0.123 I can override how code blocks render, intercepting ```mermaid fenced blocks and turning them into renderable diagram containers at build time.
1. Create the render hook
Create layouts/_default/_markup/render-codeblock.html in the project root:
{{ $lang := .Type | default "plain" }}
{{ if eq $lang "mermaid" }}
<pre class="mermaid" role="img" aria-label="Mermaid diagram">{{ .Inner }}</pre>
{{ if not (.Page.Store.Get "mermaidLoaded") }}
{{ .Page.Store.Set "mermaidLoaded" true }}
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: false,
theme: 'base',
themeVariables: {
background: '#1d1f21',
primaryColor: '#282c34',
primaryBorderColor: '#3e4147',
primaryTextColor: '#c9cacc',
lineColor: '#5a5f66',
edgeLabelBackground: '#1d1f21'
}
});
mermaid.run({ querySelector: 'pre.mermaid' });
</script>
{{ end }}
{{ else }}
{{ highlight .Inner .Type }}
{{ end }}What this template does is simple:
- When the code block language is
mermaid, it wraps the content in<pre class="mermaid">, loads mermaid v11 (ESM build) and callsmermaid.run()to render an SVG in the browser - The
.Page.Storeguard emits the<script>only once per page, so multiple diagrams still load mermaid a single time role="img"andaria-labelon the<pre>help screen readers treat it as an imagethemeVariablestunes the palette to match this blog’s dark background; I tweak the hex values to fit my own theme- Every other language keeps using Hugo’s built-in
highlightlogic, unaffected
Note: Hugo already HTML-escapes .Inner for code blocks, so the template outputs it directly — the browser’s textContent decodes it back to the original text. I escaped it a second time once and mermaid parsed the literal < entities, throwing Syntax error in text. Now I only output .Inner directly.
2. Use it in articles
In Markdown, just write a mermaid fenced code block:
```mermaid
graph TB
A[Start] --> B{Does it work?}
B -- Yes --> C[Rendered!]
B -- No --> D[Check the console]
```No Shortcodes, no front matter declaration. It just works at build time.
3. Live test
If all the diagrams below show as real graphics (not code text), the Mermaid support is live.
Flowchart
graph TB
A[Start] --> B{Does it work?}
B -- Yes --> C[Rendered!]
B -- No --> D[Check the console]Sequence diagram
sequenceDiagram
participant U as User
participant B as Blog
U->>B: Open article
B-->>U: Return HTML
Note over B: Browser loads mermaid and rendersClass diagram
classDiagram
class Article {
+title: string
+content: string
+render()
}
class MermaidHook {
+detect(lang)
+output()
}
Article --> MermaidHookGantt chart
gantt
title Blog renovation plan
dateFormat YYYY-MM-DD
section Content
Write article :done, a1, 2025-08-01, 1d
Add English version :active, a2, 2025-08-02, 2d
section Release
SEO audit :a3, 2025-08-04, 1d
Deploy :a4, 2025-08-05, 1dPie chart
pie title What this article is made of
"Chinese text" : 50
"Code examples" : 30
"Mermaid demos" : 204. Notes
- Code block render hooks require Hugo v0.123+
- Mermaid is loaded on demand from a CDN — only pages that actually contain a mermaid block pull it in
- Diagrams are rendered to SVG client-side; crawlers still see the readable raw text, so it’s SEO-friendly
5. Preventing future diagram bugs
Diagrams render in the browser, so a broken diagram never fails the build — it just shows Syntax error in text on the page. I hit this once: I applied an extra htmlEscape to .Inner in the render hook, turning the already-escaped --> into -->. The browser’s textContent decoded back to a literal >, and mermaid refused to parse it.
So I turned the check into a skill — mermaid-lint. It validates every mermaid block under content/ with the real mermaid parser, plus two static checks: literal HTML entities in the content (double-escaping) and a first line that isn’t a valid diagram type. The core is scripts/lint-mermaid.mjs:
node scripts/lint-mermaid.mjsAll green means every diagram renders; a broken one prints which article and which block is wrong and why, and the exit code is non-zero — easy to gate in CI.
One rule to follow: write raw characters in the Markdown source (-->, "label"), never HTML entities like > or ".
If the flowchart, sequence diagram, class diagram, gantt and pie charts above all rendered as real graphics, everything is working. To add this capability to my Hugo blog, all I needed was to copy that one template file.