McGarrah Technical Blog

Jekyll Mermaid Diagram Rendering: Why Client-Side Beats Plugins

5 min read

While working on my SASS architecture article, I encountered a frustrating issue: my carefully crafted Mermaid diagram wasn’t rendering. What seemed like a simple diagram display problem revealed deeper challenges with Jekyll’s syntax highlighting and diagram integration.

The Problem: Diagrams Trapped in Code Blocks

I had added a complex Mermaid diagram to visualize my SASS architecture:

```mermaid
graph TD
    V["🔧 variables.sass"]
    B["📄 basic.sass"]
    V --> B
```

But instead of a beautiful diagram, I got syntax-highlighted code. The diagram was trapped inside <pre><code class="language-mermaid"> tags.

Initial Approach: Manual JavaScript Integration

My first solution was to manually add Mermaid support to Jekyll’s default layout:

{% if page.mermaid or site.mermaid %}
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
<script>
  mermaid.initialize({ startOnLoad: true, theme: 'default' });
</script>
{% endif %}

This didn’t work because Jekyll’s Rouge syntax highlighter was processing the fenced code blocks before Mermaid could see them.

The JavaScript Workaround

I then created a more sophisticated solution that converts Rouge-processed code blocks:

document.addEventListener('DOMContentLoaded', function() {
  mermaid.initialize({ startOnLoad: false, theme: 'default' });
  
  // Find code blocks with language-mermaid and convert them
  const mermaidBlocks = document.querySelectorAll('code.language-mermaid');
  mermaidBlocks.forEach(function(block, index) {
    const content = block.textContent;
    const div = document.createElement('div');
    div.className = 'mermaid';
    div.textContent = content;
    div.id = 'mermaid-' + index;
    block.parentNode.replaceWith(div);
  });
  
  mermaid.run();
});

This worked, but felt like fighting the framework rather than working with it.

Understanding the Root Cause

The issue stems from Jekyll 4.4’s default configuration:

Rouge Syntax Highlighter

Jekyll uses Rouge to syntax-highlight code blocks. When it sees:

```mermaid
graph TD
  A --> B
```

Rouge treats this as code to be highlighted, not a diagram to be rendered. It wraps the content in <pre><code class="language-mermaid"> tags.

Jekyll Version Challenges

Older Jekyll versions had different syntax highlighting behavior. The upgrade to Jekyll 4.4 changed how fenced code blocks are processed, breaking existing Mermaid implementations that relied on different HTML output.

The Better Solution: Modern Mermaid 11 API

Instead of fighting Rouge with DOM manipulation, use Mermaid 11’s built-in support for Jekyll’s syntax highlighting:

Modern Implementation

{% if page.mermaid or site.mermaid %}
<script type="module">
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
  
  // Detect dark mode and set appropriate theme
  const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  const theme = isDarkMode ? 'dark' : 'default';
  
  mermaid.initialize({ 
    startOnLoad: true,
    theme: theme
  });
  
  await mermaid.run({
    querySelector: '.language-mermaid',
  });
</script>
{% endif %}

Key Improvements

Why Plugins Should Be Better (But Aren’t)

The Plugin Reality Check

While plugins should theoretically be the better solution, the available Jekyll Mermaid plugins have significant issues:

Plugin Advantages (When They Work)

  1. Framework Integration - Work with Jekyll’s processing pipeline
  2. GitHub Pages Compatibility - Some plugins are allowlisted
  3. Clean Separation - No custom JavaScript in layouts
  4. Maintenance - Plugin authors handle updates (in theory)

The Reality

Unmaintained plugins become liabilities. They break with framework updates and create more problems than they solve.

Why Client-Side Rendering is Better

Even if a well-maintained plugin existed, client-side rendering is architecturally superior for diagrams:

Testing the Solution

Here’s a simple diagram to verify the plugin works:

graph LR
    A[Manual JS] --> B[Workaround]
    C[Jekyll Plugin] --> D[Clean Solution]
    B --> E[Maintenance Issues]
    D --> F[Reliable Rendering]

Lessons Learned

1. Understand Your Framework

Jekyll’s Rouge highlighter processes fenced code blocks by design. Fighting this creates fragile solutions.

2. Version Upgrades Have Consequences

Jekyll 4.4 changed syntax highlighting behavior. Always test diagram rendering after framework upgrades.

3. Use Purpose-Built Tools

Dedicated plugins handle edge cases better than custom JavaScript solutions.

4. Consider Maintenance Burden

Manual JavaScript solutions require ongoing maintenance as frameworks evolve.

Implementation Steps

  1. Add Mermaid 11 script to your Jekyll layout file
  2. Use conditional loading with page.mermaid or site.mermaid front matter
  3. Write diagrams using standard fenced code blocks with mermaid language
  4. Test rendering in development with bundle exec jekyll serve
  5. Deploy - works on GitHub Pages without plugin restrictions

Conclusion

What started as a simple diagram rendering issue revealed the challenges of integrating third-party libraries with Jekyll’s processing pipeline. While plugins should be the ideal solution, unmaintained packages create more problems than they solve.

Recommendation: Use Mermaid 11’s modern ES module API for Jekyll diagrams. It’s:

The 2025 solution is cleaner than both plugins and custom workarounds.


This experience reinforced the value of understanding framework internals and choosing appropriate tools for the job.

Categories: web-development, jekyll