Integrating Mermaid JS Into Hugo

Date: 2021-02-20 · Word Count: 397 · Reading Time: 2 minutes

I had originally planned to write up an introduction to StormCrawler, but found myself wanting to draw state diagrams. Hugo doesn’t seem to have Graphviz support builtin, so I started to poke around and see what else was out there.

For those who want pure Graphviz, there’s viz-js. It’s a pretty straight forward implementation, although it hasn’t been updated in a while and the documentation suggests that people should take a look at DagreJS. I did and it didn’t really win me over.

Further poking around suggested that there’s more choice in this space than I’d realised:

There are also services which will render your graphs for you and embed an SVG, such as Gravizo. I really didn’t want anything like that, although I can see how it might be handy for embedding in GitHub wikis.

After a bit of poking around, I ended up choosing to use Mermaid. It has good docs, support for a fairly broad set of graph types and is entirely JavaScript so I can just embed it in my pages.

The Mermaid documentation recommends sourcing from jsdeliver.net. That’s all fine and good, but I like being able to survive outages in other services, so I wanted a fallback to local serving if there was a failure. After reading way too much about working around various old browsers etc, it turns out the answer was quite simple and Stack Overflow lead me to a solution in HTML 5 Boilerplate.

The final gotcha was that .Params is now $.Page.Params in Hugo. Which was rather obscurely pointed to by:

executing "shortcodes/mermaid.html" at <.Params.mermaid>: can't evaluate field mermaid in type interface {}

To create the short code, put the following in layouts/shortcodes/mermaid.html:

{{ if ($.Page.Params.mermaid) }}
<script src="//cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>!window.mermaid && document.write(unescape('%3Cscript src="/js/mermaid-8.9.1/mermaid.min.js"%3E%3C/script%3E'))</script>
<script>mermaid.initialize({startOnLoad:true});</script>
{{ end }}
<div class="mermaid">
  {{.Inner}}
</div>

The if block ensures that the JavaScript only loads if mermaid is in use in the page, which you can enable by putting mermaid: true in the parameters at the top of your page.

Then you can produce beautiful graphs by embedding code like this:

---
... Page parameters ...
mermaid: true
---

{{<mermaid>}}
sequenceDiagram
    participant John
    participant Alice
    Alice->>John: Hello John, how are you?
    John-->>Alice: Great!
{{</mermaid>}}

To get a graph like this:

sequenceDiagram participant John participant Alice Alice->>John: Hello John, how are you? John-->>Alice: Great!