7.6 Math Rendering with KaTeX or MathJax
Right, let’s talk math. You’ve probably been there: you’re writing a beautiful post about the elegant simplicity of the Lorenz attractor or the statistical sorcery behind a p-value, and you need to render an equation. Not a sad little screenshot from a word processor, but real, honest-to-goodness math that looks sharp at any zoom level.
Hugo, in its infinite wisdom, doesn’t do this natively. The underlying Markdown renderer, Goldmark, is fantastic for text but throws its hands up at a block of LaTeX like it’s an ancient cursed script. And honestly, that’s fair. LaTeX is a kind of ancient cursed script. So we have to bring in the big guns: either KaTeX or MathJax. Both are excellent JavaScript libraries that parse LaTeX syntax and turn it into beautiful math on your page. The choice between them is one of philosophy and performance.
The Core Difference: KaTeX vs. MathJax
Think of it this way: KaTeX is the brash, hyper-optimized speed demon. It was built by Khan Academy with one goal: render everything as fast as humanly possible. It achieves this by shipping only the absolute essential LaTeX features and rendering everything on the fly in the browser. The trade-off? Less comprehensive LaTeX support. If you venture into obscure packages or niche commands, KaTeX might shrug and show you an error.
MathJax is the venerable, all-knowing sage. It aims for near-complete LaTeX compatibility. Need to use that one weird command from the xy package for drawing commutative diagrams? MathJax has you covered. The trade-off? It’s bigger, slower, and can feel a bit heavier because it often has to reach back to its massive core to figure out how to render what you’ve thrown at it.
My advice? Unless you’re writing a PhD thesis in mathematics with a penchant for obscure typographical notation, use KaTeX. The speed difference is not subtle; it’s night and day. Your readers will thank you.
The Configuration: Telling Hugo to Play Nice
Here’s the first “gotcha.” By default, Hugo is terrified of the underscores and backslashes that form the backbone of LaTeX. It’ll see x_i in your Markdown and think you’re trying to italicize the letter ‘x’ followed by a random ‘i’. It will happily munch those underscores, and your beautifully written x_i will render as “xi” in the browser, leaving KaTeX very confused.
You have to explicitly tell Goldmark to leave our precious math alone. In your hugo.toml (or config.toml), you need to unleash the power of Goldmark’s extensions:
[markup]
[markup.goldmark]
[markup.goldmark.extensions]
typographer = false
[markup.goldmark.renderer]
unsafe = true
Setting typographer = false stops Hugo from getting clever with things like quotes and ellipses, which can also interfere. The unsafe = true is a horribly named but absolutely vital setting. It allows raw HTML to pass through Goldmark unscathed. Since our math blocks will ultimately be wrapped in HTML <div> and <span> tags for KaTeX/MathJax to find, this is non-negotiable. It’s safe, I promise. The name is just Hugo being dramatic.
The Implementation: Adding KaTeX to Your Site
You need to load the KaTeX CSS and JS in your site. The best place is in your base template, like layouts/partials/head.html or a dedicated math.html partial. Here’s the classic approach, loading it from a CDN:
{{ if .Page.Store.Get "hasKaTeX" }}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
{{ end }}
Notice the conditional? That’s a performance win. You only load this ~80kb of resources on pages that actually need it. To set that flag, you can use a front matter parameter like math: true and then in your template logic, check for it: {{ if .Params.math }} {{ .Page.Store.Set "hasKaTeX" true }} {{ end }}.
The auto-render.js script is the magic. Once the page loads, it scours the DOM for elements with the specific class names math (for inline) and mjx-container (for display blocks), and renders the math inside them.
Writing Math in Your Content
Now for the fun part. With the configuration out of the way, writing math is just like writing LaTeX.
Inline math is wrapped in single $ signs. This is for small bits of math that sit right in the flow of your text, like the equation $E = mc^2$ or a variable $x_i$.
The solution for the quadratic equation \( ax^2 + bx + c = 0 \) is given by $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$.
Display math is for larger, centered equations. You can use double $$ signs or the \[ ... \] syntax. I prefer the latter because the double $$ can sometimes conflict with other formatting, but both work.
$$
\begin{aligned}
\nabla \times \vec{\mathbf{B}} -\, \frac{1}{c}\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}} +\, \frac{1}{c}\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{aligned}
$$
The Final Gotcha: Code Fences and Escaping
This is the big one. If you try to write your LaTeX inside a standard Markdown code fence, Hugo will see it as code, escape all the HTML, and output it as plain text. KaTeX will never see it. You have to force it to output the raw HTML.
The solution is to use a little-known Goldmark feature: custom code block types. If you wrap your math in a code fence labeled math, Goldmark will bypass its usual escaping and output the contents directly.
```math
\frac{\partial u}{\partial t} = \nabla^2 u
```
This will output a <div> with the class math, which auto-render.js is already looking for. It’s the cleanest, most “Markdown-native” way to handle display blocks. For inline math, just stick with the $ or \( \) delimiters in your paragraph text.
And there you have it. No more excuses for blurry equation screenshots. Go forth and be irrational.