A Rehype plugin to render Mermaid diagrams in multiple themes using the Mermaid CLI.
- 🎨 Multiple Theme Support - Render diagrams in different themes
- 🚀 Server-Side Rendering - Build-time processing with Mermaid CLI
- 🔒 Consistent IDs - Hash-based stable diagram IDs
- 📦 TypeScript Support - Full type definitions included
- ⚡ Caching - Avoids re-rendering identical diagrams
- 🏎️ Browser Reuse - One shared Puppeteer browser per build, not one per diagram
- 🔢 Bounded Concurrency - Configurable parallel render limit to avoid OOM on CI
npm install rehype-mermaid-cliRequirements: Node.js 18+ (server-side only)
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import { rehypeMermaidCLI } from 'rehype-mermaid-cli';
const result = await unified()
.use(rehypeParse, { fragment: true })
.use(rehypeMermaidCLI, {
renderThemes: ['default']
})
.use(rehypeStringify)
.process(`<pre><code class="language-mermaid">graph TD; A-->B;</code></pre>`);.use(rehypeMermaidCLI, {
renderThemes: ['default', 'dark', 'forest']
}).use(rehypeMermaidCLI, {
renderThemes: ['default'],
svgClassNames: ['mx-auto', 'max-w-full'] // Add custom CSS classes
})Pass any Mermaid configuration options, including securityLevel. theme is always set from renderThemes but all other keys are forwarded as-is. Changing this config automatically busts the render cache.
// Enable loose security level (required for HTML labels / markdown strings)
.use(rehypeMermaidCLI, {
renderThemes: ['default'],
mermaidConfig: {
securityLevel: 'loose'
}
})
// Arbitrary mermaid config keys are also supported
.use(rehypeMermaidCLI, {
renderThemes: ['default'],
mermaidConfig: {
securityLevel: 'loose',
flowchart: { curve: 'basis' }
}
})// Default (works in most environments)
.use(rehypeMermaidCLI, {
renderThemes: ['default']
})
// For CI/Docker environments
.use(rehypeMermaidCLI, {
renderThemes: ['default'],
puppeteerConfig: {
headless: true,
args: ['--no-sandbox']
}
})By default rendered SVGs are written to os.tmpdir(), which is ephemeral per CI runner. Set cacheDir to a project-relative path and cache it between runs to skip re-rendering unchanged diagrams.
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
.use(rehypeMermaidCLI, {
renderThemes: ['default', 'dark'],
cacheDir: path.join(__dirname, '.mermaid-cache'),
})Then in your GitHub Actions workflow:
- name: Restore Mermaid SVG cache
uses: actions/cache@v4
with:
path: .mermaid-cache
key: mermaid-svgs-${{ hashFiles('src/content/**/*.md') }}
restore-keys: mermaid-svgs-
- name: Create Mermaid cache dir
run: mkdir -p .mermaid-cacheCap how many diagrams render in parallel within the shared browser. Lower values reduce memory pressure on constrained CI runners.
.use(rehypeMermaidCLI, {
renderThemes: ['default'],
concurrency: 3, // default: 5
})import {
rehypeMermaidCLI,
type RehypeMermaidOptions,
type MermaidConfig,
type Theme
} from 'rehype-mermaid-cli';
const options: RehypeMermaidOptions = {
renderThemes: ['default', 'dark'],
svgClassNames: ['custom-class']
};- Type:
Theme[] - Default:
['default'] - Description: Array of themes to render
Available themes: 'default', 'base', 'dark', 'forest', 'neutral', 'null'
- Type:
string[] - Default:
undefined - Description: CSS class names to add to generated SVG elements
- Type:
MermaidConfig({ securityLevel?: 'strict' | 'loose' | 'antiscript' | 'sandbox'; [key: string]: unknown }) - Default:
undefined - Description: Mermaid configuration options forwarded to the CLI.
themeis always derived fromrenderThemes. Changing this config busts the render cache automatically.
Common values:
securityLevel: 'loose'— required when diagram labels contain HTML or Mermaid markdown stringssecurityLevel: 'strict'— default Mermaid behavior (HTML-encodes labels)
- Type:
{ headless?: boolean; args?: string[]; } - Default:
{ headless: true, args: [] } - Description: Puppeteer configuration for the headless browser
// For CI/Docker environments
puppeteerConfig: {
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
// For debugging (show browser)
puppeteerConfig: {
headless: false
}- Type:
string - Default:
os.tmpdir() - Description: Directory where rendered SVG files are persisted. Files are named by a hash of the diagram content + theme + mermaid config, so unchanged diagrams are never re-rendered. Set to a project-relative path and cache it in CI for fast incremental builds.
- Type:
number - Default:
os.cpus().length(number of logical CPU cores) - Description: Maximum number of diagrams rendered in parallel within the shared browser. All renders for a given markdown file share one browser instance. Reduce this value if you see OOM errors on low-memory CI runners.
import {
rehypeMermaidCLI, // Main plugin
type RehypeMermaidOptions, // Options interface
type MermaidConfig, // Mermaid config interface
type Theme, // Theme type
defaultOptions // Default config
} from 'rehype-mermaid-cli';<pre><code class="language-mermaid">graph TD; A-->B;</code></pre><div class="mermaid-wrapper" id="mermaid-536b8b06">
<div class="mermaid mermaid-default" style="display: block;">
<svg><!-- SVG content --></svg>
</div>
</div>.mermaid-wrapper- Container for all theme versions.mermaid- Individual diagram container.mermaid-{theme}- Theme-specific classes
.mermaid { display: none; }
.mermaid-default { display: block; }
.dark-mode .mermaid-default { display: none; }
.dark-mode .mermaid-dark { display: block; }function switchTheme(theme) {
document.querySelectorAll('.mermaid').forEach(el => el.style.display = 'none');
document.querySelectorAll(`.mermaid-${theme}`).forEach(el => el.style.display = 'block');
}npm run build # Build
npm test # Test
npm run dev # Watch modeMIT License - see LICENSE file for details.