Mermaid Implementation Analysis #

Date: 2025-10-07 #

Problem #

Mermaid diagrams in content/archive/JavaBasic/_index.md were not rendering correctly after migrating from Hextra theme to hugo-tufte theme.

Root Cause Analysis #

Why CDN Approach Failed #

Attempts Made:

  1. CDN v10.9.4 (ES Module)

    <script type="module">
      import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
    </script>
    
    • Result: Syntax errors reported
    • Error: “Syntax error in text, mermaid version 10.9.4”
  2. CDN v11 (ES Module)

    <script type="module">
      import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
    </script>
    
    • Result: Still had errors
    • Note: Content worked fine with Hextra theme
  3. CDN v11 (Regular Script)

    <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
    <script>
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
    </script>
    
    • Result: Continued errors despite switching from ES module to UMD

Why Bundled Approach Works #

Final Working Implementation:

Copied Mermaid bundle from Hextra theme and used Hugo’s resource pipeline:

<!-- layouts/_default/baseof.html -->
{{ if .Page.Store.Get "hasMermaid" }}
  {{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
  <script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
    });
  </script>
{{ end }}

Bundle Details:

Key Differences: CDN vs Bundled #

AspectCDN ApproachBundled Approach
Version ControlFixed to CDN version (10.x or 11.x)Explicit version (11.3.0)
Load TimingNetwork-dependent, external requestLocal file, faster load
Module SystemES Module or UMD from CDNUMD bundle
Build ProcessDirect CDN URLHugo resource pipeline with fingerprinting
IntegrityCDN-provided (if any)Hugo-generated SRI hash
CompatibilityMay have version mismatchesTested and verified with theme
Offline DevelopmentRequires internetWorks offline
Result❌ Syntax errors✅ Works correctly

Critical Success Factors #

1. Exact Version Match #

The bundled file uses Mermaid 11.3.0, which may have specific compatibility or bug fixes not present in generic v11 CDN builds.

2. Hugo Resource Pipeline #

{{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}

3. Deferred Loading #

<script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>

4. DOMContentLoaded Wrapper #

document.addEventListener("DOMContentLoaded", function () {
  mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
});

5. Conditional Loading #

{{ if .Page.Store.Get "hasMermaid" }}

Complete Implementation #

Required Files #

1. Render Hook (layouts/_default/_markup/render-codeblock-mermaid.html)

<pre class="mermaid">
  {{- .Inner | safeHTML -}}
</pre>
{{- .Page.Store.Set "hasMermaid" true -}}

2. Base Template (layouts/_default/baseof.html)

<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
{{- partial "header.html" . -}}

<body>

{{ block "main" . }}{{ end }}

<!-- Load Katex, if necessary. -->
{{ if or .Params.math .IsHome }}
{{ partial "math.html" . }}
{{ end }}

<!-- Load Mermaid, if necessary. -->
{{ if .Page.Store.Get "hasMermaid" }}
  {{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
  <script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
    });
  </script>
{{ end }}

</body>

</html>

3. Mermaid Bundle (assets/lib/mermaid/mermaid.min.js)

Hextra Theme Implementation Reference #

Hextra theme uses the same pattern:

Location: themes/hextra/layouts/partials/scripts.html (lines 34-45)

{{/* Mermaid */}}
{{/* FIXME: need to investigate .Page.Store hasMermaid is set for homepage */}}
{{- if and (.Page.Store.Get "hasMermaid") (not .Page.IsHome) -}}
  {{- $mermaidJS := resources.Get "lib/mermaid/mermaid.min.js" | fingerprint -}}
  <script defer src="{{ $mermaidJS.RelPermalink }}" integrity="{{ $mermaidJS.Data.Integrity }}"></script>
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      const theme = document.documentElement.classList.contains("dark") ? "dark" : "default";
      mermaid.initialize({ startOnLoad: true, theme: theme });
    });
  </script>
{{- end -}}

Differences from our implementation:

  1. Hextra has additional check (not .Page.IsHome) - they note this is a FIXME
  2. Hextra supports dark theme detection - we use fixed 'neutral' theme
  3. Both use identical resource pipeline approach
  4. Both use identical defer and DOMContentLoaded patterns

Why This Matters #

Version-Specific Behavior #

Mermaid syntax evolves between versions. The content in JavaBasic/_index.md uses modern syntax:

flowchart LR
    intro@{ shape: text, label: "..." }

This @{ shape: text } syntax may not be supported or may have bugs in older/generic builds.

Build Process Integration #

Hugo’s resource pipeline:

  1. Validates file exists in assets/
  2. Generates content hash for cache busting
  3. Creates SRI hash for security
  4. Optimizes delivery with proper MIME types

CDN approach bypasses all this and relies on external infrastructure.

Debugging Lessons #

  1. “It works in theme X” → Check theme’s exact implementation
  2. CDN version mismatches → Use bundled files for consistency
  3. ES modules vs UMD → Some tools expect specific module systems
  4. Hugo best practices → Use resource pipeline, not external URLs

Maintenance Notes #

Updating Mermaid Version #

To update Mermaid in the future:

# Download specific version
curl -o assets/lib/mermaid/mermaid.min.js \
  https://cdn.jsdelivr.net/npm/mermaid@11.3.0/dist/mermaid.min.js

# Or copy from updated Hextra theme
cp themes/hextra/assets/lib/mermaid/mermaid.min.js \
   assets/lib/mermaid/mermaid.min.js

Testing Checklist #

Known Issues #

Conclusion #

Working Solution: Bundled Mermaid 11.3.0 with Hugo resource pipeline

Key Insight: Theme-bundled assets are tested and verified for compatibility. When CDN fails, copy the working bundle rather than troubleshooting version mismatches.

Success Factors:

  1. Exact version control (11.3.0)
  2. Hugo resource pipeline with fingerprinting
  3. Proper load timing (defer + DOMContentLoaded)
  4. Conditional loading via Page.Store
  5. Render hook for code block transformation