让 Hugo 博客支持 Mermaid 图表
之前写过一篇文章, 讲怎么用 Shortcodes 给博客加 ECharts 图表. 但日常写技术文章更常需要的是流程图、时序图这类"文本即图形"的图. Mermaid 正好干这个: 用几行文本描述一张图, 不用拖画布, 不用贴截图.
而且现在 Hugo 有比 Shortcodes 更优雅的方案——代码块渲染钩子 (code block render hook). 从 Hugo v0.123 开始, 可以自定义代码块的渲染, 拦截 mermaid 语言的围栏代码块, 在构建时直接输出成可渲染的图形容器.
一、创建渲染钩子
在项目根目录创建 layouts/_default/_markup/render-codeblock.html:
{{ $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 }}这个模板做的事很简单:
- 当代码块语言是
mermaid时, 把内容包进<pre class="mermaid">, 同时引入 mermaid v11 (ESM 版) 并调用mermaid.run()在浏览器端渲染成 SVG .Page.Store做"每页只输出一次 script"的守卫, 页面里有多个图时 mermaid 也只加载一份<pre>上加role="img"和aria-label, 让屏幕阅读器能识别出这是图片themeVariables把配色调成和本站暗色背景一致, 用的时候按自己的主题配色替换即可- 其他语言的代码块继续走 Hugo 内置的
highlight高亮逻辑, 互不影响
注意: Hugo 渲染代码块时已经对 .Inner 做了 HTML 转义, 模板里直接输出即可, 浏览器端的 textContent 会还原成原始文本. 我一开始在这里又手动转义了一次, 结果 mermaid 解析到 < 这类字面实体而报 Syntax error in text, 后来就只直接输出, 不再手动转义.
二、在文章里用
写 Markdown 时, 直接写 mermaid 围栏代码块即可:
```mermaid
graph TB
A[开始] --> B{支持吗?}
B -- 支持 --> C[完美渲染]
B -- 不支持 --> D[查看控制台]
```不需要任何 Shortcodes, 不需要在 front matter 里声明, 构建时自动生效.
三、实测效果
下面几个图如果都显示为真实的图形 (而不是代码文本), 说明博客的 Mermaid 支持已经生效.
流程图 flowchart
graph TB
A[开始] --> B{支持吗?}
B -- 支持 --> C[完美渲染]
B -- 不支持 --> D[查看控制台]时序图 sequenceDiagram
sequenceDiagram
participant U as 用户
participant B as 博客
U->>B: 打开文章
B-->>U: 返回 HTML
Note over B: 浏览器加载 mermaid 并渲染类图 classDiagram
classDiagram
class Article {
+title: string
+content: string
+render()
}
class MermaidHook {
+detect(lang)
+output()
}
Article --> MermaidHook甘特图 gantt
gantt
title 博客改造计划
dateFormat YYYY-MM-DD
section 内容
写文章 :done, a1, 2025-08-01, 1d
加英文翻译 :active, a2, 2025-08-02, 2d
section 发布
SEO 审计 :a3, 2025-08-04, 1d
部署上线 :a4, 2025-08-05, 1d饼图 pie
pie title 这篇文章的构成
"中文" : 50
"代码示例" : 30
"Mermaid 测试图" : 20四、注意事项
- 需要 Hugo v0.123+ 才能使用代码块渲染钩子
- mermaid 通过 CDN 按需加载, 只有页面里有 mermaid 代码块才会引入, 不影响其他页面速度
- 图形由浏览器端渲染成 SVG, 搜索爬虫看到的是可读的原始文本, SEO 友好
五、如何防止以后把图表写错
图表是浏览器端渲染的, 写错了构建不会报错, 只会让页面弹出一行 Syntax error in text. 这个坑我踩过一次: 我在渲染钩子里对 .Inner 又做了一次 htmlEscape, 结果 Hugo 已经转义过的 --> 变成了 -->, 浏览器端的 textContent 还原出字面 >, mermaid 直接解析失败.
为了以后不再写错, 我把校验做成了 skill——mermaid-lint. 它用真实 mermaid 解析器逐个校验 content/ 下所有文章里的 mermaid 块, 同时静态检查两类问题: 内容里出现字面 HTML 实体 (双重转义), 首行不是合法图类型. 核心是 scripts/lint-mermaid.mjs, 跑法:
node scripts/lint-mermaid.mjs全部通过会输出全绿, 有图写错会指出是哪篇文章第几个块、错在哪, 退出码非 0, 正好卡在 CI 里.
写图的原则就一条: Markdown 源码里写原始字符 (-->、"label"), 永远不要写 >、" 这类 HTML 实体.
如果上面的流程图、时序图、类图、甘特图和饼图都渲染出了真实图形, 那说明一切正常. 我给博客加上这套能力时, 复制的就是上面那个模板文件.