Today, we’ll be adding support for math in our Hugo-generated website!

First, let’s consider that we have two different options for rendering math:
**client-side** or **server-side**.

**Client-side rendering** involves sending the raw math code (TeX) and then
using JavaScript on the client convert the TeX code into rendered output,
whether that’s HTML, or SVG, or some other representation. Client-side rendering
is in many cases easier, as it doesn’t require any changes on the part of the
content creator, since they just add some JS and CSS to the page, which is then
rendered by the client browser, thus it enables a simple workflow.^{1}

**Server-side rendering** is done at the time of the site generation, which is
preferred as it is done once (since our site is static), the page loads much
faster (don’t need to load extra JavaScript) and renders much faster (no
client-side JavaScript running to render math).

Given this, the server-side approach is much more preferred due to its overall efficiencies, but most folks choose to use client-side rendering since the server-side rendering adds a few difficulties for the developer.

Let’s take a look to see what’s involved in making this work, specifically with Hugo — if you’re using another static site generator, or you’re writing HTML manually yourself, you may not have the same constraints as I do.

Spoiler: if you want to see what I ended up using, jump ahead to the Conclusion section at the end of this post.

## Client-side rendering

Today, we will be using KaTeX and MathJax, popular JavaScript libraries for client-side rendering of mathematical formulas and equations. From some quick research, KaTeX is faster than MathJax 2, but the MathJax 3 has improved performance significantly over its previous version.

That said, what I read suggests that MathJax has more complete support for LaTeX, so if one doesn’t work for you, try the other one.

In the end, it’s up to you which one you use, and in this post, we will add
optional support for *both* MathJax and KaTeX, and you can choose which one to
use for each individual page of your site. The support is optional so that when
you don’t enable either of them, the libraries will not be loaded, leading to a
faster loading experience for your users.

We’ll start with the instructions from this blog post, but with some additional adjustments to get more complete support for LaTeX features we need.

First, create `layouts/partials/helpers/katex.html`

with the following contents:

```
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ"
crossorigin="anonymous"
referrerpolicy="no-referrer">
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.js"
integrity="sha384-VQ8d8WVFw0yHhCk5E8I86oOhv48xLpnDZx5T9GogA/Y84DcCKWXDmSDfn13bzFZY"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/contrib/auto-render.min.js"
integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\[", right: "\\]", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
],
});
});
</script>
```

## Expand to see my current version of `katex.html`

.

```
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ"
crossorigin="anonymous"
referrerpolicy="no-referrer">
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.js"
integrity="sha384-VQ8d8WVFw0yHhCk5E8I86oOhv48xLpnDZx5T9GogA/Y84DcCKWXDmSDfn13bzFZY"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/contrib/auto-render.min.js"
integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\[", right: "\\]", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
],
});
});
</script>
```

The above contents were taken from the KaTeX website with minor changes; check back there for any newer versions of files and their hashes.

Similarly, let’s create `layouts/partials/helpers/mathjax.html`

with the
following contents per the MathJax docs:

```
<script type="text/javascript">
MathJax = {
tex: {
displayMath: [['$$', '$$'], ['\\[', '\\]']],
inlineMath: [['$', '$'], ['\\(', '\\)']],
},
};
</script>
<script
async
id="MathJax-script"
src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-mml-chtml.js"
integrity="sha384-+BSz3oj3ILMYvOBr16U9i0H4RZRmGyQQ+1q9eqr8T3skmAFrJk8GmgwgqlCZdNSo"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
```

## Expand to see my current version of `mathjax.html`

.

```
<script type="text/javascript">
MathJax = {
tex: {
displayMath: [['$$', '$$'], ['\\[', '\\]']],
inlineMath: [['$', '$'], ['\\(', '\\)']],
},
};
</script>
<script
async
id="MathJax-script"
src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-mml-chtml.js"
integrity="sha384-+BSz3oj3ILMYvOBr16U9i0H4RZRmGyQQ+1q9eqr8T3skmAFrJk8GmgwgqlCZdNSo"
crossorigin="anonymous"
referrerpolicy="no-referrer"
type="text/javascript"></script>
```

Note 1:this is a very simple usage of MathJax meant for illustration and convenience. MathJax is highly configurable, and you can choose specific modules you would like to load, based on your needs. Please see the documentation for more details and adjust for your use case if needed.

Note 2:MathJax does not provide integrity hashes for their JavaScript files; how do we compute them for this and future versions? You have several options:

The easy way: put in a fake integrity, and open the URL in Chrome; it will fail to load the resource, but when you open the Developer Console, Chrome will tell you what hash it computed for the file (which didn’t match your fake one), which you can then use yourself.

The formal way, thanks to this comment on MathJax issue about subresource integrity:

`$ curl '<URL>' -o - | openssl dgst -sha384 -binary - | openssl base64 -A`

Another blog post talks about adding the following contents
to the `layouts/partials/head.html`

which involves making a copy of your theme’s
`head.html`

and then extending it with those contents. However, the theme I’m
currently using (Papermod) already supports easy extensibility
of the `<head>`

element contents — it provides and auto-includes an empty
file `layouts/partials/extend_head.html`

(really, it only has comments) and
includes it into
`layouts/partials/head.html`

, so I don’t
have to branch `layouts/partials/head.html`

and can just create
`extend_head.html`

in my own directory.

Thus, in my case, I created a new file `layouts/partials/extend_head.html`

in my
directory which overrides the default version of `extend_head.html`

and added
the following contents into it:

```
{{ if (eq $.Page.Params.math "katex") }}
{{ partial "helpers/katex.html" . }}
{{ else if (eq $.Page.Params.math "mathjax") }}
{{ partial "helpers/mathjax.html" . }}
{{ end }}
```

Note:if your theme doesn’t support extensibility for the`<head>`

element easily as Papermod does, you will need to copy your theme’s`layouts/partials/head.html`

and extend it, as described in the other blog post. Also, consider submitting a feature request or providing a patch to your Hugo theme with a similar change as you can see in the Papermod theme for everyone’s convenience.

Finally, to enable math in particular post, we have to add one of the following to the frontmatter:

`math: katex`

to enable KaTeX for the post`math: mathjax`

to enable MathJax for the post

Here’s an example snippet of this post’s frontmatter, which is using KaTeX:

```
---
title: "Writing math with Hugo"
tags: [hugo, math]
math: katex
---
```

To switch this post to use MathJax instead, just make the following change:

```
---
title: "Writing math with Hugo"
tags: [hugo, math]
-math: katex
+math: mathjax
---
```

Let’s test it out! For starters, here’s a well-known formula:

$$ E = mc^2 $$

and here’s another equation that puts together a few fundamental constants:

$$ e^{i \pi} + 1 = 0 $$

We can also do write integrals:

$$ \int \cos(x) dx = \sin(x) + C $$

and define derivatives:

$$ f'(x) = \lim_{t \to 0} \frac{f(x + t) - f(x)}{t} $$

All seems fine, right? Let’s try a matrix:

```
$$
\begin{bmatrix}
a & b \\
c & d \\
e & f \\
\end{bmatrix}
$$
```

which renders in MathJax as:^{2}

and in KaTeX as (yes, TeX shows up on the page, mixed with HTML):

Neither of these renderings looks right; what’s going on?

Turns out, Hugo’s Markdown parser (Goldmark) converts the `\\`

sequences into
`\<br>`

, so the above TeX matrix definition results in the following HTML:

```
<p>
$$
\begin{bmatrix}
a & b \<br>
c & d \<br>
e & f \<br>
\end{bmatrix}
$$
</p>
```

However, `\\`

are newlines in TeX, and critical for matrices as well as
aligning multiple equations in a single environment. Since the KaTeX and
MathJax processing is happening client-side, by the time the JavaScript code
runs, it only sees `\<br>`

, not `\\`

, which means that KaTeX doesn’t process
the math inside those regions due to the spurious HTML tag in the middle of the
LaTeX region, while MathJax is simply missing the newline.

There are several possible solutions to this issue:

- use
`\\\\`

instead of`\\`

in Markdown source to keep`\\`

in the output - use
`\newline`

instead of`\\`

in LaTeX code, as that won’t disappear - keep using
`\\`

with a custom shortcode to avoid any processing of the`\\`

The first two options are straight-forward, and don’t
require any configuration, **but they only work with MathJax** — it won’t
help if you’re using KaTeX. Option 3 lets you use the standard `\\`

newline
markers, but requires using the custom shortcode described below, which is a bit
of an overhead.

### Adding the `math`

shortcode to handle newlines in LaTeX

I saw this identified as an issue in Hugo’s
Discourse, and although folks have provided
answers, they are not included on the site itself, but rather via external
links, and all the links are dead (and not available on the Internet Archive),
so there are no complete examples there.^{3}

As discussed in a related thread
regarding similar application of MathJax (alternative to KaTeX) together with
Goldmark, to pass through the sequence `\\`

correctly from Markdown all the way
to client-side rendering, we need to take a few additional steps.

First, we need to create a Hugo shortcode to keep the LaTeX literal code as-is,
without processing by Goldmark. Create a file named
`layouts/shortcodes/math.html`

with the following contents:

```
{{ .Inner }}
```

which basically just says to pass through the inner contents of the shortcode as-is.

Then, surround the math code block `$$ ... $$`

with the shortcode, so now it
should look like this:^{4}

```
{{< math >}}
$$
\begin{bmatrix}
a & b \\
c & d \\
e & f \\
\end{bmatrix}
$$
{{< /math >}}
```

and it will render as follows:^{2}

Success!

### Summary

To summarize, here are all the steps that we took to enable client-side rendered math in our posts.

First, one-time setup for your site:

- Create the files:
`layouts/partials/helpers/katex.html`

and`layouts/partials/helpers/mathjax.html`

- Add the handling for the
`katex`

and`mathjax`

params in the frontmatter. Depending on your theme, either:- copy your theme’s
`layouts/partials/head.html`

into your tree, or - if your theme already has a blank partial that’s already loaded into
`head.html`

and intended for extensibility, create a file matching that name instead

- copy your theme’s
- Create the file
`layouts/shortcodes/math.html`

Then, for each post:

- Specify either
`math: katex`

or`math: mathjax`

in the frontmatter - Surround math blocks that use
`\\`

with the`{{< math >}}`

shortcode

Here’s the difference in rendering the above matrix in KaTeX vs. MathJax:^{2}

KaTeX (client-side) | MathJax (client-side) |
---|---|

The renderings are quite similar with some minor differences in spacing.

## Server-side rendering

In the ideal world, we would just integrate the math processing directly into
Hugo, so that running the Hugo process, we would get both the Markdown
processing as well as the rendering the TeX math code as well. And this is what
was tried with a Hugo pull request to add KaTeX server-side math rendering
support to Hugo in Feb 2020 which integrates the
Goldmark extension for rendering KaTeX with QuickJS^{5}.

However, it was not merged, because Hugo has a policy of not allowing any code
that uses C (rather than pure Go), because it limits Hugo’s portability. The one
exception that Hugo supports right now is Sass, which is part of the
`hugo-extended`

binary, so it looks like the server-side rendering of math via
KaTeX will not be merged in the foreseeable future.^{6}

It is worth mentioning that the person who proposed that pull request switched from Hugo to Hexo, another static site generator which is written in Node, to get the benefit of server-side rendered math, because it is so important to them. I’m not ready to move away from Hugo as that will require finding another theme and adapting my entire site to use it, so I am still interested in making this work with my current Hugo workflow.

An alternative was proposed to add *goldmark-mathjax* to
Hugo; however, per a comment on that
PR,
`goldmark-mathjax`

doesn’t actually do server-side
rendering, but rather avoids the need for the `{{< math >}}`

shortcode we
needed to add previously. That said, FastAI folks are maintaining a fork of
Hugo (repo) with that patch
if you’re interested.

The author of `goldmark-qjs-katex`

ended up forking Hugo to their own
repository named *kahugo* with the necessary changes to make it
render math statically during site generation. He’s also written two blog posts
(one, two) about the development and
usage of their KaTeX-enabled Hugo fork.

### Using `kahugo`

If you’re interested in trying this out, you can build `kahugo`

via:

```
$ git clone https://github.com/graemephi/kahugo.git
$ cd kahugo
$ go build
```

Note:this requires Go 1.16 or later to build.

The post frontmatter should be changed as follows:

```
---
title: "Writing math with Hugo"
tags: [hugo, math]
-math: katex
+math: katexSsr
---
```

Update `layouts/partials/extend_head.html`

as follows:

```
{{ if (eq $.Page.Params.math "katex") }}
{{ partial "helpers/katex.html" . }}
+{{ else if (eq $.Page.Params.math "katexSsr") }}
+ {{ partial "helpers/katex_ssr.html" . }}
{{ else if (eq $.Page.Params.math "mathjax") }}
{{ partial "helpers/mathjax.html" . }}
{{ end }}
```

## Expand to see my current version of `extend_head.html`

.

```
{{ if (eq $.Page.Params.math "katex") }}
{{ partial "helpers/katex.html" . }}
{{ else if (eq $.Page.Params.math "katexSsr") }}
{{ partial "helpers/katex_ssr.html" . }}
{{ else if (eq $.Page.Params.math "mathjax") }}
{{ partial "helpers/mathjax.html" . }}
{{ end }}
```

And here’s what should be in `layouts/helpers/katex_ssr.html`

:

```
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ"
crossorigin="anonymous"
referrerpolicy="no-referrer">
```

## Expand to see my current version of `katex_ssr.html`

.

```
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ"
crossorigin="anonymous"
referrerpolicy="no-referrer">
```

Server-side rendering does not require using the `{{< math >}}`

shortcode
that we developed above for client-side rendering, but if you already have
pages using the `{{< math >}}`

shortcode that you would like to make
compatible with server-side rendering (as I do), you can update it as follows:

```
-{{ .Inner }}
+{{ if (eq $.Page.Params.math "katexSsr") }}
+ {{ .Inner | markdownify }}
+{{ else }}
+ {{ .Inner }}
+{{ end }}
```

## Expand to see my current version of `layouts/shortcodes/math.html`

.

```
{{ if (eq $.Page.Params.math "katexSsr") }}
{{ .Inner | markdownify }}
{{ else }}
{{ .Inner }}
{{ end }}
```

Then, develop and render your site with the `kahugo`

binary you built above:

```
$ kahugo server [...flags...]
```

You can then build the minified site for production locally via:

```
$ kahugo --minify [...flags...]
```

and if you happen to use Netlify (as I have been for this site), you can upload it manually using Netlify’s CLI via:

```
$ netlify deploy
```

To integrate this into Netlify build, add a command to your `netlify.toml`

such
as the following (see this blog post for an example):

```
# Warning: I have not tested this myself, and I'm not using this myself at this
# time; use at your own risk. If this works well for you, please let me know!
[build]
publish = "public"
command = "curl -sL [kahugo binary URL] && ./kahugo --minify"
[context.deploy-preview]
command = "curl -sL [kahugo binary URL] && ./kahugo --minify -b ${DEPLOY_PRIME_URL}"
```

Note:you would need to provide hosting for your pre-built`kahugo`

binary, since the repo mentioned above does not provide any binaries in the releases section. I wasn’t able to build`kahugo`

directly from the Git repo via a`go install`

or`go get`

commands; YMMV.

### Caveats of server-side rendering with `kahugo`

The downside here is that `kahugo`

, the fork of Hugo with KaTeX support, does
not appear to be actively maintained, so you will be stuck on an older version
of Hugo, or you will need to take on the maintenance of this fork and re-apply
the changes to each new release of Hugo, which I do not have the time for right
now.

Additionally, my initial testing of server-side rendering with `kahugo`

shows
that variables are not italicized by default, leading to the following rendering
of variables in a matrix:^{2}

KaTeX (server-side) | KaTeX (client-side) | MathJax (client-side) |
---|---|---|

To get equivalent behavior server-side that’s default in client-side rendering
with KaTeX and MathJax (and TeX in general), I would have to enclose each
variable in `{\it ...}`

, which significantly reduces readability, and increasing
amount of typing, so I opted not to use this approach.

### Potential server-side rendering alternatives

An issue filed on *goldmark-qjs-katex*
suggests exploring godzilla and/or otto, which are different JavaScript
runtimes, and if they don’t depend on CGO, they may be eligible to be included
into Hugo mainline by default.

However, this seems like a non-trivial undertaking, and it’s unclear if anyone is currently working on this. If you do make progress on this, please update that issue.

According to Hugo documentation, Hugo supports Pandoc
for files with the `*.pandoc`

or `*.pdc`

extension (or the `markup`

value in
front matter set to `pandoc`

or `pdc`

), and Pandoc supports rendering math in
HTML. This might lead one to assume that we can get
server-side rendering of math this way, but alas, most of the options are not
applicable:

`--mathjax`

(passed by Hugo by default) is a path to the MathJax JS file, and Pandoc just marks the TeX math in the file with the markers that MathJax will recognize`--mathml`

produces MathML, but that’s only supported by Firefox and Safari`--webtex`

converts LaTeX to URL-encoded image URLs which will be rendered by third-party servers dynamically`--katex`

has the same functionality as`--mathjax`

above`--gladtex`

may be the only viable server-side rendering option here, but it requires running an additional command-line tool`gladtex`

after`pandoc`

is run (which is invoked by`hugo`

), which complicates local development since`hugo server`

never exits

There are Pandoc filters for both MathJax as well as KaTeX, so that may be helpful to convert TeX to SVG and display math inline in the HTML documents.

Pandoc seems to be also well-integrated in Quarto, a publishing system for scientific and technical documents. Quarto also supports Hugo, so there’s a potential integration / reuse possibility there as well, without having to switch to another static site generator and look for another theme, and adopt all existing posts to a new blog engine.

## Conclusion

Having spent a lot of time researching server-side rendering for math, and trying out the available options for Hugo (which I’ve documented above), I decided to use client-side rendering with KaTeX for now, which is how this page is currently rendered, at the time of publication.

Hope this article and the pointers here have been useful!

If you end up using anything described here, or if you end up pursuing server-side rendering, please let me know how it works for you.

There are also client-side Markdown processors as well as client-side syntax highlighting libraries. ↩︎

All illustrative renderings in this post (both client-side and server-side) were previously included on the page using declarative shadow DOM with templates and slots in an attempt to isolate the KaTeX and MathJax rendering in a separate DOM subtree, but we ended up needing to contain them via

`<iframe>`

to isolate the independent renderings entirely, because without`<iframe>`

, shadow DOM was unable to isolate CSS entirely and their effects were leaking out of their shadow DOM container and impacted the rest of the page and other rendered chunks, leading to very confusing results. ↩︎This is why Stack Overflow likes to avoid link-only answers, because inevitably, external sites go down, disappear, or their domain names are lost, sold, etc. and then the only reference for the solved problem is now gone. Always copy the relevant portion of a code snippet so that your answer is self-contained! ↩︎

Note that to include the shortcode literal into a post, you need to escape the internal contents of

`{{...}}`

with`/* ... */`

; for details, see this very helpful blog post. ↩︎See also additional discussion on another Hugo issue:

*Consider markdown extensions for math typesetting in Hugo*. ↩︎