本文记录一次 Hexo 博客的实际故障排查与修复过程。遇到的问题包括:代码块中的超长代码行超出显示范围、Mermaid 流程图无法正常渲染并显示炸弹图标。最终通过调整 CSS、修改 Hexo 语法高亮配置,以及编写自定义 Mermaid 渲染脚本,解决了这两个问题。
本文提供完整、可复制的配置和代码,方便遇到相同问题的用户参考。示例基于本文末尾列出的实际测试环境;不同主题版本或插件组合可能需要调整。
一、故障现象与测试环境
1. 故障一:代码块中的长代码行超出显示范围
在博客文章中插入代码块后,部分超长代码行没有自动换行,直接延伸到代码块右侧,超出了页面的正常显示范围。在手机等窄屏设备上,这种问题会更加明显。
预期效果:
- 代码在当前代码块宽度内自动折行。
- 保留原始代码的空格和缩进。
- 不需要手动修改文章中的代码内容。
- 尽量不影响现有的语法高亮与复制功能。
2. 故障二:Mermaid 流程图显示炸弹图标
在 Markdown 文章中插入 Mermaid 流程图后,网页没有显示正常的流程图,而是显示类似下面的错误:
Syntax error in text
Parse error on line 1:
flowchart LR U["QQ 私聊"] --> NC["N
-------------^
Expecting 'NEWLINE', got 'NODE_STRING'
看到 Syntax error in text,很容易先怀疑 Mermaid 语法写错了。但本次排查发现,原始流程图语法可以通过独立解析测试;故障发生在页面自动渲染流程中,传给 Mermaid 的源码被合并成了一行,导致解析器无法识别预期的换行。
3. 本次测试环境
| 项目 | 环境 |
|---|---|
| 博客框架 | Hexo 7.3.0 |
| Hexo CLI | 4.3.2 |
| Node.js | 24.1.0 |
| 操作系统 | Windows 11 环境下的 Windows 终端 |
| 博客主题 | Hexo ParticleX |
| Mermaid 版本 | 11 |
| Mermaid 插件 | hexo-markdown-mermaid |
如果你的主题版本、代码块结构或渲染插件与本文不同,可能需要调整 CSS 选择器或脚本。
二、修复代码块超长行溢出
1. 找到 ParticleX 的样式文件
进入 Hexo 博客项目根目录,打开:
themes/particlex/source/css/main.css
建议先备份文件,再进行修改。
2. 在文件末尾添加 CSS
将以下代码添加到 main.css 的最末尾:
/* ParticleX:修复代码块长行溢出 */
.code-content {
white-space: pre-wrap !important;
overflow-wrap: anywhere;
word-break: break-word;
overflow-x: hidden;
max-width: 100%;
box-sizing: border-box;
}
/* 兼容代码高亮和行号区域 */
.code-content pre,
.code-content code,
.code-content .hljs-ln-code {
white-space: pre-wrap !important;
overflow-wrap: anywhere;
word-break: break-word;
}
3. 这段 CSS 的作用
| CSS 属性 | 作用 |
|---|---|
white-space: pre-wrap |
保留原有空白和换行,同时允许长行自动折行 |
overflow-wrap: anywhere |
必要时允许较长的连续字符串换行 |
word-break: break-word |
为长字符串提供额外的换行兼容性 |
max-width: 100% |
限制元素宽度不超过父容器 |
box-sizing: border-box |
让元素的宽度计算包含内边距和边框 |
这里最重要的是:
white-space: pre-wrap !important;
overflow-wrap: anywhere;
原有代码块的 white-space 设置可能会阻止长行换行,因此需要覆盖该行为。
注意: 本方案针对使用 .code-content 类名的代码块结构。如果你的 ParticleX 版本使用了不同的 DOM 类名,需要根据浏览器开发者工具中的实际元素调整选择器。
这段 CSS 只负责显示效果,不会自动修改 Markdown 源文件。视觉折行也不等于向源代码中插入真正的换行符。
4. 重新生成并刷新博客(或用Git控制,本文以PowerShell为例)
在博客根目录打开 PowerShell,执行:
npx hexo clean
npx hexo generate
如果需要本地预览,可以执行:
npx hexo server
最后在浏览器中按 Ctrl + F5 强制刷新。若博客部署在远程服务器,还需要重新部署生成后的文件。
三、修复 Mermaid 流程图显示炸弹图标
本次最终采用的方案包含三个关键部分:
- 调整 Hexo 的语法高亮配置。
- 使用 HTML 容器承载 Mermaid 源码。
- 添加自定义脚本,从服务器返回的原始 HTML 提取源码,再调用
mermaid.render()生成 SVG。
1. 安装 Mermaid 插件
进入博客根目录,也就是存在 _config.yml 和 package.json 的目录,执行:
npm install hexo-markdown-mermaid --save
这一步用于安装 Mermaid 集成插件,帮助网页加载 Mermaid.js 并提供初始化配置。
2. 修改 Hexo 根目录的 _config.yml
需要修改的是:
_config.yml
而不是:
themes/particlex/_config.yml
本次原有的相关配置为:
syntax_highlighter: highlight.js
highlight:
line_number: true
auto_detect: false
tab_replace: ''
wrap: true
hljs: false
prismjs:
preprocess: true
line_number: true
tab_replace: ''
在本次 Hexo 7.3.0 与 ParticleX 环境中,将内置语法高亮器设置为空,并让 Mermaid 语言绕过普通高亮处理。相关配置修改为:
syntax_highlighter:
highlight:
line_number: true
auto_detect: false
tab_replace: ''
wrap: true
hljs: false
exclude_languages:
- mermaid
prismjs:
preprocess: true
line_number: true
tab_replace: ''
mermaid:
version: 11
startOnLoad: false
theme: default
注意以下细节:
syntax_highlighter:必须顶格书写,值留空。exclude_languages必须放在highlight:下面,并比highlight:多缩进两个空格。mermaid:必须顶格书写,与highlight:、prismjs:并列。startOnLoad: false用于关闭默认自动启动的渲染流程,后续由自定义脚本调用渲染 API。- 不要在同一个 YAML 文件中重复创建多个
highlight:或mermaid:配置块;如果这些配置已经存在,应当合并。
3. 修改文章中的 Mermaid 写法
原本的流程图可能使用 Markdown 代码围栏:
```mermaid
flowchart LR
U[QQ 私聊] --> NC[Napcat Docker 容器]
NC --> AB[AstrBot Docker 容器]
AB -->|HTTP API:11434/v1| TS[Tailscale 私有网络]
TS --> OL[Windows 11 · Ollama]
OL --> GPU[Peach-Framboise GGUF 模型]
GPU --> VRAM[RTX 3060 8GB]
```
但本次环境中,自动渲染流程存在异常。为了绕过普通代码块处理,最终改用 HTML 容器直接承载 Mermaid 源码。
将文章中的流程图替换为以下完整代码,不要再用三个反引号包住它:
<div class="mermaid">
flowchart LR
U["QQ 私聊"] --> NC["Napcat Docker 容器"]
NC --> AB["AstrBot Docker 容器"]
AB -- "HTTP API: 11434/v1" --> TS["Tailscale 私有网络"]
TS --> OL["Windows 11 - Ollama"]
OL --> GPU["Peach-Framboise GGUF 模型"]
GPU --> VRAM["RTX 3060 8GB"]
</div>
这样生成的 HTML 将包含 .mermaid 容器,而不是普通代码块生成的 <pre><code> 结构。
如果文章中有多个 Mermaid 图表,同样可以使用多个独立的 <div class="mermaid">...</div> 容器。
四、排查过程:如何确认问题出在哪里
如果按照前面的步骤操作后仍然看不到流程图,不建议马上继续修改 CSS 或安装其他插件。本次故障的关键是逐层验证。
1. 确认 Mermaid.js 是否加载成功
打开显示异常的博客页面,按 F12 打开浏览器开发者工具,切换到 Console,执行:
typeof mermaid
预期结果:
'object'
如果返回 undefined,说明当前页面没有可用的 mermaid 对象,需要检查脚本加载和执行情况。
本次排查中,该命令返回了 'object',因此可以排除单纯的脚本未加载问题。
2. 确认当前页面是否存在 Mermaid 容器
执行:
document.querySelectorAll('.mermaid').length
如果页面中只有一个流程图,预期结果为:
1
这只能说明浏览器找到了容器,并不能证明流程图已经正确渲染。
如果仍使用原来的代码块选择器:
document.querySelectorAll('code.mermaid').length
它返回 0 并不奇怪,因为最终方案使用的是 <div class="mermaid">,而不是 <code class="mermaid">。选择器必须与实际 DOM 结构对应。
3. 检查服务器返回的原始 HTML
在 Console 中执行:
const html = await fetch(location.href, {
cache: 'no-store'
}).then(r => r.text());
const i = html.indexOf('flowchart LR');
console.log(
i === -1
? '没有在网页源 HTML 中找到 flowchart LR'
: html.slice(Math.max(0, i - 200), i + 700)
);
这段代码会重新读取服务器返回的 HTML,并打印 Mermaid 源码附近的内容。
正常情况下,应当看到类似结构:
<div class="mermaid">
flowchart LR
U["QQ 私聊"] --> NC["Napcat Docker 容器"]
NC --> AB["AstrBot Docker 容器"]
</div>
如果这里出现异常的 <pre>、<code> 包装,或者源码被高亮器逐行处理,那么问题还没有解决。
本次排查中,原始 HTML 的结构是正确的,源码也没有残留反引号,因此不能简单归咎于 Markdown 语法。
4. 为什么 SVG 存在,不代表流程图成功?
之前执行过:
document.querySelector('.mermaid svg') !== null
结果为:
true
但页面仍然显示炸弹图标。这说明不能仅凭 SVG 是否存在判断渲染成功:解析失败时,Mermaid 也可能生成包含错误提示的 SVG。
更可靠的判断标准是:
- 页面显示的是正常流程图,而不是错误图标。
- Console 没有 Mermaid 解析错误。
- 节点、连线和标签都正确显示。
5. 关键对比:自动渲染失败,直接渲染成功
自动渲染时报错:
Parse error on line 1:
flowchart LR U["QQ 私聊"] --> NC["N
Expecting 'NEWLINE', got 'NODE_STRING'
错误内容表明,本来应该分行的源码进入解析器后,变成了类似下面的单行内容:
flowchart LR U["QQ 私聊"] --> NC["Napcat Docker 容器"] ...
但通过读取原始 HTML 并直接调用 mermaid.render(),却可以成功生成正常流程图。
这证明了两件事:
- 原始源码是可解析的。
- 页面原本的自动渲染过程没有正确处理这份源码。
因此,最终方案不再依赖原来的自动渲染路径,而是从服务器读取原始 HTML,提取源码后直接调用 Mermaid 的渲染 API。
五、最终方案:编写自定义 Mermaid 渲染脚本
这是本次解决 Mermaid 问题的核心。
1. 创建脚本目录
在博客根目录下创建:
scripts/
如果该目录已经存在,直接使用即可。然后在其中创建:
scripts/mermaid-fix.js
最终目录结构应类似:
myblog/
├── _config.yml
├── package.json
├── scripts/
│ └── mermaid-fix.js
├── source/
└── themes/
└── particlex/
Hexo 会自动加载项目根目录 scripts/ 中的脚本。
2. 将以下代码完整复制进去
请直接替换 scripts/mermaid-fix.js 的全部内容,不要把代码追加在旧版脚本后面。
hexo.extend.injector.register(
'body_end',
`
<script>
(async function () {
async function renderMermaidBlocks() {
if (!window.mermaid) {
console.error('[Mermaid] Mermaid.js 未加载');
return;
}
try {
// 从服务器重新获取未经前端脚本修改的 HTML
const response = await fetch(location.href, {
cache: 'no-store'
});
if (!response.ok) {
throw new Error(
'获取原始 HTML 失败:HTTP ' + response.status
);
}
const html = await response.text();
// 解析原始 HTML,而不是直接读取当前页面 DOM
const sourceDoc = new DOMParser().parseFromString(
html,
'text/html'
);
const sourceBlocks = Array.from(
sourceDoc.querySelectorAll('.mermaid')
);
const targetBlocks = Array.from(
document.querySelectorAll('.mermaid')
);
if (sourceBlocks.length !== targetBlocks.length) {
console.warn(
'[Mermaid] 源码容器数量与页面容器数量不同',
sourceBlocks.length,
targetBlocks.length
);
}
for (
let i = 0;
i < Math.min(
sourceBlocks.length,
targetBlocks.length
);
i++
) {
const source = sourceBlocks[i].textContent
.replace(/\\r\\n?/g, '\\n')
.trim();
const target = targetBlocks[i];
if (!source) {
console.warn(
'[Mermaid] 第 ' + (i + 1) + ' 个图表源码为空'
);
continue;
}
try {
console.log(
'[Mermaid] 源码检查:',
JSON.stringify(source)
);
const result = await window.mermaid.render(
'mermaid-fixed-' + i + '-' + Date.now(),
source
);
// 用正常生成的 SVG 替换错误图或旧内容
target.innerHTML = result.svg;
if (typeof result.bindFunctions === 'function') {
result.bindFunctions(target);
}
console.info(
'[Mermaid] 图表渲染成功:',
i + 1
);
} catch (error) {
console.error(
'[Mermaid] 第 ' + (i + 1) + ' 个图表渲染失败:',
error
);
}
}
} catch (error) {
console.error('[Mermaid] 初始化失败:', error);
}
}
// 等页面及 Mermaid.js 加载完成
if (document.readyState === 'complete') {
renderMermaidBlocks();
} else {
window.addEventListener(
'load',
renderMermaidBlocks,
{ once: true }
);
}
})();
</script>
`,
'default'
);
3. 这段脚本做了什么?
第一阶段:等待 Mermaid.js 加载。
if (!window.mermaid) {
console.error('[Mermaid] Mermaid.js 未加载');
return;
}
避免在 Mermaid.js 不存在时直接调用渲染函数。
第二阶段:重新获取服务器返回的 HTML。
const response = await fetch(location.href, {
cache: 'no-store'
});
const html = await response.text();
这里读取的是当前文章从服务器返回的 HTML,而不是已经经过页面脚本处理的 DOM。
第三阶段:解析原始 HTML。
const sourceDoc = new DOMParser().parseFromString(
html,
'text/html'
);
通过 DOMParser 创建独立的文档对象,从原始 HTML 中读取 Mermaid 容器。
第四阶段:提取源码并保留换行。
const source = sourceBlocks[i].textContent
.replace(/\\r\\n?/g, '\\n')
.trim();
由于这段代码位于外层 JavaScript 模板字符串中,反斜杠需要经过外层字符串处理。不要在复制时随意删改反斜杠。
第五阶段:直接调用 Mermaid 渲染 API。
const result = await window.mermaid.render(
'mermaid-fixed-' + i + '-' + Date.now(),
source
);
这里不再调用原来报错的 mermaid.run(),而是明确将提取出的源码交给 mermaid.render()。
第六阶段:将生成的 SVG 插入页面。
target.innerHTML = result.svg;
如果返回了交互绑定函数,也会调用它:
if (typeof result.bindFunctions === 'function') {
result.bindFunctions(target);
}
渲染成功后,Console 会出现类似日志:
[Mermaid] 图表渲染成功: 1
数字代表成功渲染的图表序号。
六、重新生成博客并验证
完成配置、文章与脚本修改后,在博客根目录执行:
npx hexo clean
npx hexo generate
npx hexo server
如果已经启动本地服务器,需要先按 Ctrl + C 结束旧进程,再重新执行命令。
部署到远程服务器时,继续使用原有的 Hexo 部署流程即可。
打开文章后,按 Ctrl + F5 强制刷新浏览器。
验证清单
| 检查项目 | 预期结果 |
|---|---|
| 超长代码行 | 不再横向溢出 |
| Mermaid 脚本 | 正常加载 |
.mermaid 容器 |
能被浏览器找到 |
| Mermaid 源码 | 保留原始换行 |
| Console 渲染日志 | 出现渲染成功信息 |
| 流程图 | 正常显示节点、连线和标签 |
| 页面布局 | 不会因为代码块造成横向溢出 |
建议至少测试一篇包含长代码块的文章,以及一篇包含 Mermaid 流程图的文章。
七、常见问题与注意事项
1. 修改配置后仍然显示旧页面
可能是浏览器缓存或旧的生成文件尚未清除。建议重新执行:
npx hexo clean
npx hexo generate
然后按 Ctrl + F5 强制刷新浏览器。如果博客部署在 CDN 或反向代理之后,还需要确认访问到的是最新部署的文件。
2. typeof mermaid 返回 undefined
说明当前页面没有可用的 mermaid 对象。应检查:
- Mermaid.js 脚本请求是否成功。
- 浏览器 Network 面板中相关资源是否返回成功状态。
- 是否存在脚本加载错误。
- 自定义渲染脚本是否在 Mermaid.js 加载之前执行。
3. document.querySelectorAll('code.mermaid').length 返回 0
如果文章已经改用:
<div class="mermaid">
...
</div>
那么 code.mermaid 返回 0 是正常的。此时应该检查:
document.querySelectorAll('.mermaid').length
选择器必须与实际 HTML 结构对应。
4. SVG 存在,但仍然显示炸弹图标
不要仅凭下面的结果判断渲染成功:
document.querySelector('.mermaid svg') !== null
错误提示本身也可能包含 SVG。更可靠的方式是观察实际显示的图形,以及 Console 中是否出现解析错误。
5. 普通代码块与 Mermaid 图表是两个不同的问题
这次排查涉及两种不同的渲染路径:
- 普通代码块主要由 CSS 和代码高亮结构决定显示效果。
- Mermaid 图表需要先将流程图源码解析为图形,再由浏览器显示 SVG。
因此,不要试图用一条 CSS 规则同时解决所有问题。
6. 升级主题或插件后,需要重新验证
自定义脚本依赖 .mermaid 选择器。如果将来更新主题、切换 Markdown 渲染器或更换 Mermaid 插件,建议重新检查:
- 原始 HTML 的结构。
- Mermaid 源码是否保留换行。
- 自定义脚本是否仍能找到容器。
- 是否引入了重复的自动渲染流程。
本方案是在当前 Hexo 7.3.0、ParticleX 和 Mermaid 11 环境下验证成功的,不意味着所有不同版本的主题都必须使用完全相同的脚本。
八、总结
这次修复最重要的经验,不是某一条 CSS 或 JavaScript 代码,而是如何将复杂的前端问题逐层拆解。
第一个问题:代码块长行溢出
通过检查代码块的显示方式,使用 CSS 覆盖 white-space 与换行规则,解决了长行超出页面范围的问题。
第二个问题:Mermaid 显示炸弹图标
通过一系列验证,逐步确认:
- Mermaid.js 已经加载。
- 原始 HTML 结构正确。
- 流程图源码本身能够通过语法检查。
- 原有自动渲染过程出现了换行相关的解析错误。
- 从服务器原始 HTML 提取源码后,直接调用
mermaid.render()可以成功生成正常流程图。
最终采用自定义渲染脚本,从原始 HTML 中提取 Mermaid 源码,保留换行并直接生成 SVG,成功解决了问题。
如果你遇到与本文类似的故障,建议按照“检查配置 → 检查原始 HTML → 检查浏览器运行状态 → 验证源码 → 修复渲染流程”的顺序排查,而不是一开始就反复修改语法或安装插件。
这种排查方式不仅适用于 Hexo,也适用于其他依赖 Markdown 渲染、语法高亮和前端 JavaScript 的静态博客系统。
参考资料
本文记录的是实际排查得到的可用方案。由于不同版本的主题、插件、Markdown 渲染器与语法高亮器可能生成不同的 HTML 结构,如果你的环境与本文不同,请优先通过浏览器开发者工具检查生成结果,再调整相应代码。