Find the Formula Rendering Issue of Hugo
April 18, 2025 · 7 min read · Page View:
A record of the formula rendering issue of Hugo
If you have any questions, feel free to comment below.
最近在写数学相关的博客的时候,发现 mathjax
的渲染效果不尽人意,经常会在渲染的时候出现非预期的渲染效果,虽然只是极个别的公式,对整体影响并不大,当然我可以放着不解决,继续进行接下来的事情,或者简单将公式通过截图的形式放在文章里,也无伤大雅。但是凡事得想长远,想本质,今天大可以为了这一个公式采用省事的方法解决,那么它不会是我写的最后一个公式,未来可能还会有更多,因为这是一个长期的问题,如果放任不管,可能下次遇到类似的问题,我可能还是会采用省事的方法解决,那么这个问题就会一直存在,所以我认为放下手头的事,集中解决这个问题是值得的。
问题的判断 #
我分析了一下这个问题:
$$
\begin{align*}
R_{xx}(t_1,t_2) &= E\{X(t_1)X^{*}(t_2)\} \\
&= \iint x_1x_2^{*} f_x(x_1,x_2,\tau = t_1 - t_2)dx_1dx_2\\
&= R_{xx}(t_1 - t_2)=\hat{R}_{xx}(\tau)=R_{xx}^{*}(-\tau),
\end{align*}
$$
公式本身的问题 #
这是 markdown 中常见的 multi-line 公式,在线的环境下是完全可以渲染的,因此可以排除公式本身的错误。
公式渲染库的问题 #
那么问题只可能出现在 Hugo 对于数学公式的渲染上,我的 hugo 采用的是 mathjax 作为渲染库,因此我自然而然得怀疑是否是 mathjax 导致的问题,因此我首先验证了在 mathjax 中是否可以采用 \begin{align*}
的环境来渲染公式,答案是肯定的。
页面解析的问题 #
既然环境没有问题,那么问题大概率出现在 chrome 对于页面的解析上,在常见的 markdown 文档到 html 的解析中,按照以往的经验来看,最容易出现问的是几个特殊的字符,比如 *
和 _
。因此我将目光转向了这两个字符。
转义 #
我最先搜索了是否有和我类似的情况,但是解决方案全部是转义,但是我的预期是公式必须要保证和原生的 latex 公式保持一致,因此使用转义的方式我排除了(因为我想大概率这也会是一个长期的问题,如果每次都通过转义的方式来实现,那么之后一直会通过转义的方式实现,这为今后 markdown 文档的通用处理与迁移制造了很大的麻烦)。
切换公式渲染库 #
通过查找,我看到 hugo 的 issue 中有人说轻量型的 Katex 引擎可以解决这个问题,因此我将博客原本的 mathjax 引擎替换为了 Katex 引擎,结果令人失望,问题依然存在。但也不是完全没有收获,通过比较不难发现,在禁止浏览器的 cache 的情况下,Katex 的渲染速度大概是 mathjax 速度的 5 倍,这意味着在一个完全没有 CDN cache 命中的情况下(详见我的另一篇文章),一个完全陌生的用户访问我的博客时能够获得更好的体验,因此我决定今后博客的公式渲染全部基于 Katex。
替换表达 #
寻根溯源,我对可能引起问题的符号进行了替换尝试,我通过查询 katex 的文档,将 *
替换为 \ast
,在最小单元中显示成功了。但是整体的公式并未成功渲染,我通过 inspect 查看 html 发现,_
符号被解析成了 <em>
标签。
因此这时我想到的是写一个 javascript 的正则匹配,在 katex 渲染之前将公式中的 _
符号进行替换,当然这样是完全不够的,还需要同时替换解决 {}
以及 \\
符号的问题。
在我开始着手之前,确保没有重复解决问题是必须要考虑的,因此我再次搜索相关的问题,无论是 stackoverflow 亦或是 issue,大部分人都使用转义的方法,有少部分人为了防止公式被解析,将公式放在单独的 <div>
里。我再次在官方文档里一条一条文档参数寻找,最终我追溯到了 hugo 的 Goldmark Extensions: Passthrough 参数,发现一年多以前官方就注意到了这个问题,通过分界符的判断将公式环境下的内容不进行解析。然而即使是最近的众多第三方主题 issue 里,大部分人仍分享自己采用转义的方式,因此保证对问题的分析和信息的检索是很有必要的。
最终解决方案 #
添加配置项
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
block = [['\[', '\]'], ['$$', '$$']]
inline = [['\(', '\)']]
同时我也将信息同步在了 stackoverflow 中。
最终,无需任何调整与转义,latex 中的公式在浏览器中不会进行 markdown 的解析,而是直接通过 katex 进行渲染。
$$ \begin{align*} R_{xx}(t_1,t_2) &= E\{X(t_1)X^{\ast}(t_2)\} \\ &= \iint x_1x_2^{\ast} f_x(x_1,x_2,\tau = t_1 - t_2)dx_1dx_2\\ &= R_{xx}(t_1 - t_2)=\hat{R}_{xx}(\tau)=R_{xx}^{\ast}(-\tau), \end{align*} $$如果你采用 workflows 的方式部署,记得确保流程里的 hugo 版本与本地保持一致,以免出现本地渲染结果与线上不一致的情况。
Related readings
If you find this blog useful and want to support my blog, need my skill for something, or have a coffee chat with me, feel free to: