Documentation and technical writing often require visual elements to communicate complex concepts effectively. Mermaid diagrams offer a powerful solution: write diagrams as code, render them as beautiful SVGs. This guide shows you how to integrate Mermaid into your Hugo workflow using partials, creating a seamless experience for both content creators and readers.
What is Mermaid?
Mermaid is a JavaScript-based diagramming tool that transforms simple text definitions into interactive diagrams. Instead of wrestling with traditional diagramming software, you write diagram syntax directly in your markdown:
Key advantages:
- Version Control Friendly: Diagrams are text, so they diff and merge like code
- Maintainable: Easy to update and refactor diagram content
- Consistent Styling: Automatic, professional appearance
- Format Flexibility: Supports flowcharts, sequences, Gantt charts, and more
Mermaid Diagram Types
Mermaid supports various diagram types for different use cases:
Flowcharts
Perfect for process flows and decision trees:
Sequence Diagrams
Ideal for API interactions and system communications:
Git Graphs
Excellent for development workflow documentation:
Hugo Integration Architecture
Integrating Mermaid into Hugo requires a strategic approach that balances performance, maintainability, and functionality:
Method 1: Hugo Partials Integration (Recommended)
The cleanest approach uses Hugo partials to conditionally load Mermaid resources.
Step 1: Create the Mermaid Detection Logic
Create layouts/_default/_markup/render-codeblock-mermaid.html
to automatically detect Mermaid blocks:
<div class="mermaid">
{{ .Inner | htmlEscape | safeHTML }}
</div>
{{ .Page.Store.Set "hasMermaid" true }}
Step 2: Add Mermaid Loading to Base Template
Add the following to your layouts/_default/baseof.html
(or theme’s equivalent):
{{ 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,
theme: 'base',
themeVariables: {
'primaryColor': '#567ebd',
'primaryTextColor': '#ffffff',
'primaryBorderColor': '#2b4c7e',
'lineColor': '#2b4c7e',
'secondaryColor': '#567ebd',
'tertiaryColor': '#f0f4f8'
}
});
</script>
<!-- Optional: Custom Mermaid styling -->
<style>
.mermaid {
text-align: center;
margin: 2rem 0;
}
.mermaid svg {
max-width: 100%;
height: auto;
}
/* Dark mode compatibility */
@media (prefers-color-scheme: dark) {
.mermaid {
filter: invert(1) hue-rotate(180deg);
background-color: transparent;
}
}
</style>
{{- end -}}
Step 3: Configuration Options
Add Mermaid configuration to your config.yml
:
params:
mermaid:
theme: "default" # Options: default, dark, forest, neutral
# Optional: Local asset path if not using CDN
# localPath: "/js/mermaid.min.js"
Method 2: Code Block Integration (Standard Approach)
The most common and Hugo-native approach is using standard markdown code blocks with the mermaid
language identifier. This works automatically with the render hook we created above:
Usage in markdown:
```mermaid
flowchart TD
A[User Login] --> B{Credentials Valid?}
B -->|Yes| C[Generate Token]
B -->|No| D[Show Error]
C --> E[Redirect to Dashboard]
```
This approach:
- Works automatically with the render hook
- Follows standard markdown conventions
- Is supported across different static site generators
- Requires no additional shortcode configuration
Advanced Configuration
Performance Optimization
<!-- layouts/partials/mermaid.html with performance enhancements -->
{{- if .Page.Store.Get "hasMermaid" -}}
<!-- Preload Mermaid for better performance -->
<link rel="preload" href="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js" as="script">
<script>
// Lazy load Mermaid when diagrams are in viewport
document.addEventListener('DOMContentLoaded', function() {
const mermaidBlocks = document.querySelectorAll('.mermaid');
if (mermaidBlocks.length === 0) return;
// Intersection Observer for lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !entry.target.dataset.processed) {
initializeMermaid();
entry.target.dataset.processed = 'true';
}
});
}, { rootMargin: '50px' });
mermaidBlocks.forEach(block => observer.observe(block));
function initializeMermaid() {
if (window.mermaid) {
mermaid.init();
} else {
// Load Mermaid dynamically
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js';
script.onload = () => {
mermaid.initialize({
startOnLoad: false,
theme: '{{ .Site.Params.mermaid.theme | default "default" }}'
});
mermaid.init();
};
document.head.appendChild(script);
}
}
});
</script>
{{- end -}}
Theme Integration
For better integration with your site’s design:
/* Custom Mermaid theming */
.mermaid-container {
background: var(--background-color, #fff);
border: 1px solid var(--border-color, #e2e8f0);
border-radius: 8px;
padding: 1.5rem;
margin: 2rem 0;
overflow-x: auto;
}
.mermaid-caption {
text-align: center;
margin-top: 1rem;
color: var(--text-muted, #64748b);
font-size: 0.9rem;
}
/* Responsive diagrams */
@media (max-width: 768px) {
.mermaid svg {
transform: scale(0.8);
transform-origin: top left;
}
}
Security Considerations
When using Mermaid, consider these security aspects:
<!-- Content Security Policy friendly version -->
<script nonce="{{ .Hugo.CSPNonce }}">
// CSP-compliant initialization
document.addEventListener('DOMContentLoaded', function() {
// Sanitize diagram content
const mermaidElements = document.querySelectorAll('.mermaid');
mermaidElements.forEach(element => {
const content = element.textContent;
// Basic content validation
if (content.includes('<script') || content.includes('javascript:')) {
element.innerHTML = '<p>⚠️ Diagram content blocked for security</p>';
return;
}
});
mermaid.initialize({
securityLevel: 'strict',
startOnLoad: true
});
});
</script>
Local Asset Management
For production sites requiring strict asset control:
Step 1: Download Mermaid
# Download specific version
curl -o static/js/mermaid.min.js \
https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js
Step 2: Update Partial
<!-- layouts/partials/mermaid.html with local assets -->
{{- if .Page.Store.Get "hasMermaid" -}}
<script src="{{ "js/mermaid.min.js" | relURL }}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
mermaid.initialize({
startOnLoad: true,
theme: '{{ .Site.Params.mermaid.theme | default "default" }}'
});
});
</script>
{{- end -}}
Troubleshooting Common Issues
Diagrams Not Rendering
Check initialization:
// Debug version for troubleshooting
mermaid.initialize({
startOnLoad: true,
logLevel: 'debug', // Add this for debugging
theme: 'default'
});
Verify content detection:
<!-- Add debugging to render hook -->
{{- (.Page.Store.Set "hasMermaid" true) -}}
<!-- DEBUG: Mermaid block detected -->
<div class="mermaid">
{{- .Inner | safeHTML -}}
</div>
Content Security Policy Conflicts
<!-- CSP-friendly version -->
<script {{ with hugo.CSPNonce }}nonce="{{ . }}"{{ end }}>
// Your Mermaid initialization code
</script>
Mobile Responsiveness
/* Better mobile handling */
.mermaid {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.mermaid svg {
min-width: 100%;
max-width: none;
}
Real-World Examples
Documentation Workflow
API Documentation
Performance Monitoring
Track Mermaid’s impact on your site:
// Performance monitoring
const mermaidStart = performance.now();
mermaid.initialize({
startOnLoad: true,
theme: 'default'
});
mermaid.init().then(() => {
const mermaidTime = performance.now() - mermaidStart;
console.log(`Mermaid rendered in ${mermaidTime.toFixed(2)}ms`);
// Optional: Send to analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'mermaid_render', {
'render_time': Math.round(mermaidTime),
'diagram_count': document.querySelectorAll('.mermaid').length
});
}
});
Deployment Considerations
Build Process Integration
# GitHub Actions example
name: Build and Deploy Hugo Site
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build site
run: |
hugo --minify
# Verify Mermaid integration
grep -r "mermaid" public/ || echo "No Mermaid diagrams found"
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
Content Validation
#!/bin/bash
# validate-mermaid.sh - Check for valid Mermaid syntax
echo "Validating Mermaid diagrams..."
# Find all Mermaid code blocks
find content -name "*.md" -exec grep -l "```mermaid" {} \; | while read file; do
echo "Checking $file..."
# Extract Mermaid blocks and validate syntax
awk '/```mermaid/,/```/' "$file" | grep -v '```' | \
while IFS= read -r line; do
if [[ -n "$line" ]]; then
# Basic syntax validation
if [[ "$line" =~ ^[[:space:]]*$ ]]; then
continue
fi
echo " Diagram line: $line"
fi
done
done
echo "Mermaid validation complete."
Conclusion
Integrating Mermaid into Hugo using partials provides a clean, maintainable solution for adding diagrams to your static site. The key benefits of this approach:
- Performance: Only loads Mermaid when needed
- Maintainability: Centralized configuration through partials
- Flexibility: Support for various diagram types and customization
- SEO-Friendly: Diagrams render as searchable SVG content
Best Practices Summary:
- Use auto-detection through render hooks for seamless integration
- Implement lazy loading for better performance
- Consider local asset hosting for production environments
- Add proper error handling and CSP compliance
- Monitor performance impact with analytics
This approach has proven effective for technical documentation, research publications, and any content requiring visual process representation. The combination of Hugo’s build speed and Mermaid’s diagram capabilities creates a powerful documentation platform.
© 2025 Seyed Yahya Shirazi. All rights reserved.