Hugo MathML with Temml
This is a tutorial on how to display math notation with MathML in Hugo using Temml.
Lacking any native solution to display math on websites, developers have made many alternatives. Some chose to render their equations down to gifs or pngs, and others bundled scripts like MathJax and KaTeX in their sites to do the job—both options are lacking. Pre-rendering math leads to undesirable fuzzy text, and large scripts lead to slow sites. Since 20231, we do have a native solution to render math on the web: MathML. Given that browsers can now quickly render crisp math, it’s makes sense why everybody is switching to MathML…
Nobody has switched to MathML yet.2
MathJax still reigns supreme rendering math on the web, but it doesn’t have to be that way.
I think part of the reason why MathML has faced slow adoption is that the tooling sucks. Most website builders—if they support math at all—don’t support MathML. I use Hugo to generate my web site and have managed to hack MathML support in using the Temml project. Here’s what the above math looks like in my editor:
$$
\text{Check this out!}\quad \sum_{n=1}^{x-1} (2n+1) = x^2 \quad \text{This is MathML}
$$
It’s the same familiar LaTeX-like notation used by MathJax.
How the Hack Works
Hugo → HTTP request with raw math → NodeJS server with Temml → HTTP response with MathML → Hugo
Temml is a JavaScript library which converts LaTeX-like math notation into MathML code. Of course, Hugo doesn’t support running JavaScript code, but it does support making HTTP requests. So, we setup Hugo to send raw math to a local server running NodeJS and Temml. This local server processes the raw math into MathML and returns it3. Hugo places the returned math on the page.
Tutorial
This tutorial assumes you already have a Hugo project setup with a working theme. Otherwise, check Hugo’s guide Additionally, ensure you’re using Hugo version 0.141.0 or greater.
To supplement this tutorial, I’ve put up a minimal working hugo site with MathML on github.
Prerequisites:
- Hugo v0.141.0+
- NodeJS (any recent version should do)
- An existing Hugo project
Enable Passthrough
The passthrough render hook allows text like $abc 123$
to be captured and processed by custom logic.
This must first be enabled, then it can be customized to send the captured text to the local server.
Enable in site config file by adding the following lines:
[markup]
[markup.goldmark]
[markup.goldmark.extensions]
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
block = [['$$', '$$']]
inline = [['$', '$']]
Create the hook file:
layouts/_default/_markup/render-passthrough.html
And paste the contents:
{{.Inner}}
{{ if eq .Type "block" }}
{{ warnf "Got block math at %s: %s" .Position .Inner }}
{{ else }}
{{ warnf "Got inline math at %s: %s" .Position .Inner }}
{{ end }}
Now, in a content markdown file place a string enclosed by $$
like:
$$
Test test math
$$
And add math: true
to the frontmatter of the page.
Build the site and you should see the message within the dollar signs printed to the terminal.
WARN Got inline math at ".../content/index.md:0:1": Test test math
If this all works, proceed to the next step.
Setup Temml Server
The following template takes the raw math, encodes it in base-64, sends the base64 to the local server and places the response on the page.
Replace the contents of render-passthrough.html
with the following script:
If you build the site as-is, you will see errors about not being able to connect.
Next, setup the Temml compile server. Create a directory called mathcompile
at the root of your project and save the following file there:
Now, you will need to download the Temml files from the github releases. Download the latest version and unzip the archive into a temporary folder. Copy temml.mjs
into mathcompile
.
Now, running the server.mjs
script with Node should start the math compile server.
node ./mathcompile/server.mjs
Now, running the server and building the site should convert any raw math into MathML. Temml provides some extra styling and consistency tweaks which must be added in the next step.
Installing Styles and Fonts
Inside your Hugo project’s static
directory, create a subdirectory called temml
.
Go back to the temporary folder with the Temml release and copy Temml-Latin-Modern.css
and Temml.woff2
to the new temml
directory.
Download the Latin Modern Math font and place it in the temml
directory
Now, you need to setup the site to load these files. There’s a whole lot of fancy stuff that can be done with Hugo pipes, but to keep it simple, add the following to the document head:
{{ if .Params.math }}
<link rel="stylesheet" href="./temml/Temml-Latin-Modern.css">
{{ end }}
Your theme may have an integrated way to do this. In the case of the example repo, the theme used layouts/partials/custom_head.html
for it. Check with your theme’s documentation.
If everything worked correctly, you should have MathML setup for your Hugo project.
More Info
The Temml docs is a good source of information if you want to configure things like other fonts. It’s very well written.
It gets annoying to start both the math compile server and Hugo at the same time, so I made a simple shell script to start and stop both:
#!/bin/bash
set -e
node ./mathcompile/server.mjs & math_pid=($!)
trap "kill '$math_pid'" ERR
trap "kill '$math_pid'" EXIT
./hugo server -wDFE "$1"
Showcase
Most of these were sourced from Temml’s many many test pages. Here’s another.
-
Long time coming. The MathML specification was first published in the late ’90s and saw some browser implementation. It’s been supported in Firefox since then, but has seen limited support in Chromium and WebKit. In 2023, support finally came to the other browsers. ↩︎
-
That statement is an exaggeration: some people use MathML now. A good example is on Frédéric Wang’s Blog. Note that Frédéric is one of the folks who pushed for MathML to be reimplemented ↩︎
-
Hugo will cache the HTTP responses aggressively so there’s no need to worry about sending tons of HTTP requests when rebuilding the site. Math will only be re-rendered when modified. ↩︎