KaTeX in Hugo

Because of the Forum settings, I can’t put it more than two links for my first post (?!), which is why you can’t see most of the links.


I (and many others) want to user KaTeX on our Hugo website. In my markdown I want to write

# Some topic

Let $n$ be a natural number, and define $m := n^2 + 1$. Then ...

KaTeX can automatically render the equations between dollar signs. The problem is, that when one wants to use it in Hugo, first Goldmark renders the markdown to HTML, and then KaTeX works with the result. Unfortunately, Goldmark already destroys equations, as for example underscores are converted to <em> tags.

The issure was already raised in a previous question here: How to render math equations properly with KaTeX

The proposed solution was to either

  • use a shortcode, so that one needs to write things like {{< katex >}}$m := n^2 + 1${{< /katex >}}.
  • use a render hook for code blocks, which wouldn’t even allow inline formulas.

Well, everyone who regularly writes LaTeX knows that this is absolutetly no solutions, as I can’t write this hugo amount of text every few words just to put an $n$ or $x + 1$. It is also unfeasible because I convert the Markdown file to a PDF with some software, which obviously doesn’t understand the shortcode syntax.

There are so many people who need to use LaTeX on their website in the most simple way. Please, add a way, or tell me a way, to make it possible to just write my $x+1$ formula in the Markdown files. Here are two proposed solutions:

  • add a way so that KaTeX can hook in correctly to render the formulas
  • Goldmark already has a KaTeX extension, called goldmark-katex, linked on their official repository! You just need add a way of adding Goldmark extensions!

Please, I and many others need this as soon as possible.

my solution is found there GitHub - gj52/HugoSample: Hugo Sample

Is it possible to adapt that with KaTeX? Don’t want to use MathJax…

@jmooring what are your thoughts?

Use a short code for inline expressions/equations, and use a code block render hook for display mode.

The markdown syntax for fenced code block is easier than typing shortcode opening and closing tags.

There’s a KaTeX example here:

git clone --single-branch -b hugo-forum-topic-40998 https://github.com/jmooring/hugo-testing hugo-forum-topic-40998
cd hugo-forum-topic-40998
hugo server

I’ve seen that… as I said, it’s not a solution.

Wouldn’t it be easy to add GitHub - FurqanSoftware/goldmark-katex: Goldmark extension for math and equations to Hugo? It would solve all the problems.

Yes, it is. It’s just not the one you want.

If it only was more overhead for me that would be fine, but I also want to convert the Markdown to PDF using other software which doesn’t understand Hugo Shortcodes.

I have now created a fork that optionally enables the goldmark-katex extension which is linked in the official goldmark repository. It only adds a few lines, you can find all changes here. By default, it is disabled. You can enable it in hugo.toml. I might post a tutorial on all of this at some point.

Now I have two questions to an experienced Hugo developer; @jmooring could you maybe help out quickly?

  1. Would you consider adding this to the main repo? Or is it hopeless to make a pull request?
  2. If you don’t add it, I would like to make this fork available to people who need LaTeX. For this, I want to package it into e.g. a .deb package. How do you create these packages for Hugo? Could you make the scripts that are used for this available? That would be awesome!

Thank you so much!

I am not. I contribute here and there. I built your fork and updated my config:

KaTeX = true

I placed this in markup:


I build the site and I see this:


So I added this to the head element of my baseof.html template:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">

<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>

<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"

Now I see this:


So unless I’m missing something, this depends on loading 130 KB of dependencies, whether CDN or self-hosted.


I understand your desire to not use a code block render hook, or a shortcode, but I’m not convinced this adds enough value to warrant inclusion.

If it embedded an SVG it would be more interesting. The Math API services uses MathJax under the hood to embed an SVG or PNG. It requires a shortcode or render hook, but embedding an SVG image in the rendered output means that no JS is required.

You are welcome to open an issue (a proposal), but I wouldn’t spend any time on a PR until the proposal has been accepted.

With respect to your questions about packaging, I’m the wrong guy. Even if I were the right guy, I would encourage you to seek answers elsewhere.

Oh then I made a mistake with the variable. I only tried it with true in the Hugo code (i.e. activated by standard). Then it works. It creates KaTeX-HTML at generation-time. You only need to include the KaTeX CSS in your stylesheet then, no scripts.

So from what I understand, then it’s “more interesting”. I’ll fix the variable thing and try to open an issue / pull request.

(Oh yeah, just to explain: It doesn’t generate an SVG as you say, but it generates HTML. And the KaTeX stylesheet then makes this look like LaTeX. Thus it also scales when you increase font-size, and so forth.)

This (my standard math test) didn’t work:

\begin{array} {lcl}
  L(p,w_i) &=& \dfrac{1}{N}\Sigma_{i=1}^N(\underbrace{f_r(x_2
  \rightarrow x_1
  \rightarrow x_0)G(x_1
  \longleftrightarrow x_2)f_r(x_3
  \rightarrow x_2
  \rightarrow x_1)}_{sample\, radiance\, evaluation\, in\, stage2}
  \\\\\\ &=&
  \rightarrow x_i
  \rightarrow x_{i-1})G(x_i
  \longleftrightarrow x_{i-1})}{p_a(x_{i-1})}}_{stored\,in\,vertex\, during\,light\, path\, tracing\, in\, stage1})\dfrac{G(x_k
  \longleftrightarrow x_{k-1})L_e(x_k
  \rightarrow x_{k-1})}{p_a(x_{k-1})p_a(x_k)})

But works on the KaTeX playground.

So I’m guessing that github.com/FurqanSoftware/goldmark-katex needs some work. With no open or closed issues or PRs, and only 2 stars, it might not be ready for prime time.

Hold off. I didn’t realize you had branched. Give me a bit to take it for a spin, again.

OK, sorry about that. The original test worked with the master branch because that particular equation is not modified by GoldMark.

Using the correct branch, I can see that this:


is transformed to this:

  <p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><mrow><mi mathvariant="normal">Γ</mi><mo stretchy="false">(</mo><mi>s</mi><mo stretchy="false">)</mo></mrow></mfrac><msubsup><mo>∫</mo><mn>0</mn><mi mathvariant="normal">∞</mi></msubsup><mfrac><msup><mi>u</mi><mrow><mi>s</mi><mo>−</mo><mn>1</mn></mrow></msup><mrow><msup><mi>e</mi><mi>u</mi></msup><mo>−</mo><mn>1</mn></mrow></mfrac><mi mathvariant="normal">d</mi><mi>u</mi></mrow><annotation encoding="application/x-tex">
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4271em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">Γ</span><span class="mopen">(</span><span class="mord mathnormal">s</span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.44445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4143em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">0</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9119em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.5904em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">u</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">u</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">s</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.7693em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathrm">d</span><span class="mord mathnormal">u</span></span></span></span></span></p>

And it displays properly with only the CSS as a dependency.

But my standard math test still fails.

Yes, I’m over-communicating on this, but it has me somewhat interested now.

If I remove all the newlines from my standard math test, it works. Either the goldmark-katex extension, or the KaTeX api, has issues with it. The test equation as-is works with the KaTex playground, MathJax, and the Math API.

In that CSS is still required, there is precedence for that with the Chroma syntax highlighter. By default the required CSS is placed inline as element style attributes. But with some CSPs blocking inline styling, it is more common now to use Chroma with an external style sheet, generated by hugo gen chromastyles....

However, even if goldmark-katex worked flawlessly, and the external style sheet requirement was deemed to be acceptable, there’s still a question of cost vs utility. There are always future costs of adding something (maintenance, support, reporting issues upstream, risk of non-responsive upstream maintainers, etc.).

I would guess that for every 100 site authors that need syntax highlighting, 1 needs math rendering. Just a guess, but I’ll bet I’m in the ballpark.

And, we have shortcodes and render hooks to handle most of those, one of which embeds an SVG.

I think the strongest case you can make is that JS is not required, nor is an external service.

Thanks for looking at all of this! I will look into this and see where things go wrong.

Hello, thanks for looking into this. Did you submit a PR to main? I’d also be interested in this feature, and from scrolling through some forums online it looks like many more people would be.

I believe I found a solution that would satisfy both the maintainers and the community wanting to use LaTeX in Markdown.

The idea is to add a Goldmark extension that finds LaTeX in Markdown and emits it as plain text, making sure the LaTeX expressions don’t get parsed as Markdown. That way, users can use Katex client side scripts to render the LaTeX in the browser. In contrast to using goldmark-katex, this solution avoids shipping a JavaScript engine inside Hugo, which requires CGO (which is discouraged in the Hugo repo), as the rendering happens entirely in the browser.

I opened a feature proposal here: Add LaTeX in Markdown support to Hugo · Issue #10894 · gohugoio/hugo · GitHub

Would be great to hear your feedback on this!

Just to be clear as to why this is, so I don’t come of as a grumpy person turning down working solutions for no good reason: CGO means the extended version only (need the C build chain for the target platform), but it also means that we must be extra careful about the dependency and the bindings (because of security concerns, a C program has no restrictions on what it can do to the system) and it it a pain to handle upstream fixes, which historically has meant that we needed to maintain those bindings ourselves – which doesn’t scale very well. Also, the bindings in questions hadn’t seen an update in several years, which didn’t look very promising.

Om a related note, I did some experiments using goja some time ago, which looked promising for the simpler tasks:

1 Like

Don’t get me wrong, I think these are reasonable concerns! Shipping a JavaScript engine for this one feature might be a bit too much. For this reason I decided to implement the same functionality but with client side rendering, which adds a bit of overhead to viewing the site but nothing crazy and certainly something that people using Katex have accepted in the past.