Introduction

This blog is managed with the workflow shown below.

  graph LR
  org-mode -- <a href="https://ox-hugo.scripter.co/">ox-hugo</a> --> markdown -- <a href="https://gohugo.io/">Hugo</a> --> HTML

When you want to include a diagram like the flowchart above, what methods come to mind?

Surely, you’re not thinking about making diagrams in PowerPoint or similar tools, exporting them as images, and… calling it a day?

That’s a D-tier move

Figure 1: PowerPoint is truly D-tier

Figure 1: PowerPoint is truly D-tier

Putting that aside, since I wasn’t very knowledgeable about ways to make diagrams—especially when using ox-hugo—I asked Gemini 2.5 Pro to do a deep research on the topic. As a result, we created the following Tier List together. 1

表 1: Diagram Creation Methods in ox-hugo—Tier List by Gemini 2.5 Pro & me
S
mermaid.js via Hugo Render Hooks
A
Org Babel + Hugo Kroki
Pure Org Babel Execution (PlantUML/Ditaa)
B
Create with GUI Tools (like Excalidraw), insert as SVG/PNG image
Legacy Hugo Shortcodes
C
iFrame embed
ASCII Art (ditaa, GoAT)
D
🤮 Made with M$ products (PowerPoint, Visio) + binary insertion 🤮

As shown in the table, my personal recommendation is to use mermaid.js. However, the setup isn’t all that straightforward and can be a bit of a hassle, so I’m writing down the steps here.

Two Methods to Use mermaid.js

  flowchart TD
    B{Are you a Mermadian?} -- Yes --> C{Want to render in the browser?}
    B -- No --> D[<a href="https://www.microsoft.com/ja-jp/microsoft-365/powerpoint">That's D-tier</a>]
    C -- Yes --> E[<a href="#方法2-hugoのレンダーフックでmermaid-dot-jsを使う">Method 2</a>]
    C -- No --> F[<a href="#方法1-org-babel-でpng-svgを生成">Method 1</a>]

Method 1: Use org-babel to Generate png/svg

Preparation

  1. Install mermaid-cli
  2. Install ob-mermaid in Emacs and configure it as explained in their README.

Usage

By executing mermaid code in an org-babel block like the example below, the generated image file will be inserted into your hugo content.

Source (org-mode)
#+begin_src mermaid :file ./images-in-content/mermaid-sample.svg :results file :exports results
graph LR;
    A--> B & C & D
    B--> A & E
    C--> A & E
    D--> A & E
    E--> B & C & D
#+end_src

#+RESULTS:
[[file:./images-in-content/mermaid-sample.svg]]
Output (hugo)

Method 2: Use Hugo Render Hooks to enable mermaid.js

In Method 2, you use Hugo’s render hooks to directly render mermaid.js diagrams on the browser. The main advantage of this method is that you don’t need to generate image files—just write the code to display the diagram.

Preparation

  1. Following the official Hugo documentation, create layouts/_markup/render-codeblock-mermaid.html and add:
          <pre class="mermaid">
          {{ .Inner | htmlEscape | safeHTML }}
          </pre>
    
  2. Add the following to layouts/partials/extend_head.html:
          {{ if .Store.Get "hasMermaid" }}
            <script type="module">
              import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
              mermaid.initialize({ startOnLoad: true });
            </script>
          {{ end }}
    
    If you want to prevent your site from breaking due to future library changes, you can specify the version in the URL, for example: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.esm.min.mjs'.

Usage

Source (org-mode)
#+begin_src mermaid :exports code
graph LR;
    A--> B & C & D
    B--> A & E
    C--> A & E
    D--> A & E
    E--> B & C & D
#+end_src
Output (hugo)
  graph LR;
    A--> B & C & D
    B--> A & E
    C--> A & E
    D--> A & E
    E--> B & C & D

Personally, I recommend Method 2 for three main reasons:

  1. No local files are generated
  2. The diagram’s theme can be matched to hugo’s (for how, refer to the supplement below)
  3. You can use hyperlinks and other extended features of HTML

(Supplements)

(Supplement 1) Methods That Didn’t Work

As described in the ox-hugo documentation,

#+begin_mermaid
flowchart TD
    Start --> Stop
#+end_mermaid

If you write it like this, ox-hugo converts it to markdown as:

<div class="mermaid">

flowchart TD

    Start --> Stop

</div>

This isn’t the code fence I was expecting. As a result, HTML like the following is produced, and you get a Syntax error in text:

<div class="mermaid"></p><p>flowchart TDA &ndash;&gt; B{Is it working?}B &ndash; Yes &ndash;&gt; C[Great!]B &ndash; No &ndash;&gt; D[Check Console]</p></div>

I didn’t pursue #+begin_mermaid any further, but if someone else managed to make it work, I’d love to hear about it.

(Supplement 2) Centering mermaid Diagrams

By default, diagrams are left-aligned, so I add the following CSS to center them:

#+begin_export hugo
<style>
.mermaid {
    text-align: center;
}
</style>
#+end_export

(Supplement 3) Switch mermaid.js Themes for Light/Dark Mode

By updating the script in layouts/partials/extend_head.html as follows, you can change the default mermaid.js theme according to light or dark mode:

{{ if .Store.Get "hasMermaid" }}
  <script type="module">
    import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
    function updateMermaidTheme() {
      let mermaidTheme = 'default';
      if (localStorage.getItem("pref-theme") === "dark" ||
          (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches &&
           !localStorage.getItem("pref-theme"))) {
        mermaidTheme = 'dark';
      }
      mermaid.initialize({
        startOnLoad: true,
        theme: mermaidTheme
      });
    }
    updateMermaidTheme();
  </script>
{{ end }}

Conclusion

In this article, I explained two methods to use mermaid.js with ox-hugo.

Both methods let you embed diagrams in your ox-hugo content, but I personally recommend using the render hook approach.

That’s all for how to use mermaid.js with ox-hugo!


  1. To be honest, Gemini 2.5 Pro rated “Hugo Kroki” as S-tier and “mermaid.js” as A-tier, but that makes no sense to me—so I swapped them. ↩︎