Docusaurus Prerender Mermaid Plugin: Static & Accessible Mermaid Diagrams
Information about the article

Author: Dmitry Dugarev
A Docusaurus plugin that pre-renders your Mermaid diagrams into static SVG or PNG images at build time.
This solves common issues with client-side Mermaid rendering, such as slow performance, content layout shift (CLS), and especially the accessibility and SEO problems caused by missing alt text, captions, text descriptions and poor semantic HTML structure.
Demo-Example
Here is how you would write a diagram in your .md or .mdx file:
- Mermaid Code
- Generated HTML
- Rendered image
Use the meta fields in the frontmatter-like block to add id, alt text, caption, and aria-describedby for accessibility.
```mermaid
---
id: company-flow
alt: A flowchart showing the company process.
caption: The official company development process.
width: 300px
descriptionId: company-flow-desc
date: 2025-11-03T16:00:00+01:00
---
graph TD
A[Sales] --> B(Development);
B --> C{Testing};
C --> D[Deployment];
```
The above Mermaid markdown is transformed into the following HTML at build time when you have both themes (light & dark) enabled:
<figure id="company-flow" class="static-mermaid-figure">
<!-- This <img> will not be on the page if only the light theme is enabled in the config -->
<img
width="500px"
class="mermaid-light"
src="/img/diagrams/company-flow-de-light.svg"
alt="A flowchart showing the company process."
aria-labelledby="company-flow-caption"
aria-describedby="company-flow-desc"
/>
<!-- This <img> will not be on the page if only the dark theme is enabled in the config -->
<img
width="500px"
class="mermaid-dark"
src="/img/diagrams/company-flow-de-dark.svg"
alt="A flowchart showing the company process."
aria-labelledby="company-flow-caption"
aria-describedby="company-flow-desc"
/>
<figcaption id="company-flow-caption">
The official company development process.
</figcaption>
</figure>
Depending on your configuration, only the <img> for the light or dark theme will be rendered, or both if the theme switch is enabled.
When switching themes, Docusaurus automatically shows the correct image and hides the other one without any JavaScript - it happens instantly via CSS.
Because the hidden image has display: none, it is removed from the accessibility tree and ignored by screen readers, so only the visible image is read out.
Here is how the rendered diagram looks on the website:
Description of the company process diagram.
This diagram illustrates the main stages of our company process, starting from Sales, moving to Development, followed by Testing, and culminating in Deployment.
What is this and Why?
By default, Docusaurus and @docusaurus/theme-mermaid render diagrams on the client side. This means every visitor's browser has to run the Mermaid.js library to parse your diagram text and turn it into an image.
This approach has several problems:
- Poor Performance: It adds extra JavaScript to your site, increasing load times.
- Layout Shift: A flash of unstyled code appears before the diagram renders, causing your page content to "jump" (a bad Core Web Vital score).
- No Static Export: Diagrams don't appear in RSS feeds, social media previews, or other non-JS environments.
- Accessibility Issues: The client-side renderer doesn't add
alttext or captions, making diagrams inaccessible to screen readers. It also doesn't use semantic HTML elements like<figure>and<figcaption>and it is impossible to link to external text descriptions by usingaria-describedby. - SEO Problems: Search engines can't index the content of your diagrams, potentially hurting your SEO.
This plugin fixes all of that by doing the rendering once at build time (npm run build). It generates static <img> tags, resulting in a lightning-fast, zero-layout-shift experience for your users. For accessibility, it wraps each diagram in a proper <figure>, and supports captions in <figcaption>, alt text, and aria-describedby for text descriptions.
Features
- Static Rendering: Converts
mermaidblocks into static<img>tags. - Dark/Light Mode Support: Automatically renders light and dark versions of your diagrams based on your Docusaurus theme settings.
- Accessible: Generates an HTML
<figure>wrapper with<img>and<figcaption>for screen readers, filling outalttext, caption andaria-describedbyusing your diagram's metadata. - Metadata Support: Use a frontmatter-like block inside your diagram to add an
id,alttext,caption, and link to a text description viaaria-describedby. - Fully Configurable: Integrates directly with your
docusaurus.config.ts, includingthemeConfig.mermaid. - Caching: Already-rendered diagrams are skipped, making subsequent builds fast.
- Customizable Output: Choose between SVG and PNG formats, set output directories, and pass custom arguments to the Mermaid CLI.
- Concurrency Control: Configure how many diagrams to render in parallel for optimal build performance.
How it Works
The plugin operates in three stages:
Text description for the diagram "Overview of the Docusaurus Prerender Mermaid
Plugin workflow."
This flowchart illustrates the three main stages of the Docusaurus Prerender Mermaid Plugin workflow:
-
Build Time (Main Plugin):
- Scans your content directories (
docs,blog, etc.) for allmermaidblocks. - Reads your
docusaurus.config.tsto find your light and dark theme names for Mermaid. If you havemermaid.config.jsonfile, it uses the settings from there instead ofdocusaurus.config.ts. - If your color mode switch is disabled via
themeConfig.mermaid.disableColorMode, it only renders the one default theme defined inthemeConfig.colorMode.defaultMode. - It calls
@mermaid-js/mermaid-cli(mmdc) to render two images for each diagram (e.g.,diagram-de-light.svganddiagram-de-dark.svg) if the theme switch is enabled, or just one image if disabled. - It saves these images to the
static/img/diagramsdirectory (or your configuredoutputDir). - Caches rendered diagrams to speed up future builds.
- When Docusaurus starts the build process, it copies these images from
static/to the finalbuild/directory automatically.
- Scans your content directories (
-
Content Transformation (Remark Plugin):
- During the Markdown-to-HTML conversion, the remark plugin intercepts the
mermaidblock. - It replaces the code block with an HTML
<figure>structure. - Inside the figure, it inserts two
<img>tags pointing to the static files (or one, if the theme switch is disabled). prerender: falsediagrams are skipped and left as-is for the client-side renderer.- It fills out accessibility attributes like
alt,aria-label, andaria-describedbyusing the metadata provided in the diagram block. - If a caption is provided, it adds a
<figcaption>element below the images. - In the dev mode (
npm run start), it shows the original mermaid renderer for live previewing and faster edits, which are modified with the same structure as the static output using<figure>,<img>, and<figcaption>to match the build output as closely as possible.
- During the Markdown-to-HTML conversion, the remark plugin intercepts the
-
Client Side (Browser):
- The plugin injects a tiny CSS file that uses Docusaurus's
[data-theme='dark']attribute to show the correct<img>and hide the other one. This switch is instant and requires zero JavaScript.
- The plugin injects a tiny CSS file that uses Docusaurus's
Installation & Setup
You need to install the plugin from the npm registry:
npm install @barrierenlos/docusaurus-prerender-mermaid
You must add the plugin to two places in your docusaurus.config.ts:
- The main plugin in the root
pluginsarray. - The remark plugin in your docs/blog/pages preset options.
// docusaurus.config.ts
import type { Config } from '@docusaurus/types';
// 1. Import the remark plugin
import remarkMermaidStatic from '@barrierenlos/docusaurus-prerender-mermaid/remark';
const config: Config = {
// ...
i18n: {
defaultLocale: 'de',
locales: ['de', 'en', 'ru'],
},
themeConfig: {
// ...
// The plugin reads this config automatically!
mermaid: {
theme: { light: 'neutral', dark: 'dark' },
options: {
fontFamily: 'Arial, sans-serif',
},
},
},
// 2. Add the main plugin
plugins: [
[
'@barrierenlos/docusaurus-prerender-mermaid',
{
// Plugin options...
contentPaths: ['docs', 'legal'], // Dirs to scan
outputDir: 'static/img/diagrams', // Where to write SVGs
outputFormat: 'svg', // 'svg' or 'png'
// concurrency: 4, // How many to render at once. Defaults to CPU count
mmdcArgs: ['-b', 'transparent'], // Extra mmdc args
// other options...
},
],
// ... other plugins
],
presets: [
[
'classic',
{
docs: {
// 3. Add the remark plugin
beforeDefaultRemarkPlugins: [remarkMermaidStatic],
// ... other docs options
},
blog: {
// 3. Add the remark plugin (if you use mermaid in blog)
beforeDefaultRemarkPlugins: [remarkMermaidStatic],
// ... other blog options
},
// ...
} satisfies Preset.Options,
],
],
};
export default config;
Configuration Options
All options are optional and are passed to the main plugin in docusaurus.config.ts.
| Option | Description | Default |
|---|---|---|
contentPaths | An array of content directories to scan for diagrams, relative to siteDir. | ['docs', 'blog'] |
outputDir | The directory to output rendered images to, relative to the static directory. | 'img/diagrams' |
outputFormat | The output format. Can be 'svg' or 'png'. | 'svg' |
configFile | Path to a physical mermaid.config.json file. If provided, this overrides themeConfig.mermaid.config for base styles. | 'mermaid.config.json' |
concurrency | The number of diagrams to render concurrently. | os.cpus().length |
mmdcArgs | An array of additional string arguments to pass to the mmdc CLI. | ['-b', 'transparent'] |
outputSuffixes | The suffixes to append for light and dark themes. | { light: '-light', dark: '-dark' } |
Configuration Examples
Default (Minimal) Configuration
If you are happy with all the defaults, you just need to register the plugin. The remark plugin needs no options.
import remarkMermaidStatic from 'docusaurus-prerender-mermaid/remark';
const config: Config = {
// ...
plugins: [
'docusaurus-prerender-mermaid',
// ...
[
'@docusaurus/plugin-content-docs',
{
id: 'your-custom-id',
path: 'your-custom-path',
// ...
beforeDefaultRemarkPlugins: [remarkMermaidStatic],
},
],
],
presets: [
[
'classic',
{
docs: {
beforeDefaultRemarkPlugins: [remarkMermaidStatic],
// ...
},
// ...
},
],
],
};
Advanced (Complete) Configuration
This example changes the content directories, output path, and adds a custom scale factor to mmdc.
import remarkMermaidStatic from 'docusaurus-prerender-mermaid/remark';
const config: Config = {
// ...
plugins: [
[
'docusaurus-prerender-mermaid',
{
contentPaths: ['docs', 'legal', 'src/pages'],
outputDir: 'static/assets/mermaid',
outputFormat: 'png',
concurrency: 4,
mmdcArgs: ['-b', 'transparent', '--scale', '1.5'],
},
],
// ...
],
presets: [
[
'classic',
{
docs: {
beforeDefaultRemarkPlugins: [remarkMermaidStatic],
// ...
},
},
],
],
// ...
};
Mermaid Metadata
You can add a metadata block (similar to frontmatter) to the top of any mermaid block. This gives you fine-grained control over accessibility and styling.
Available Metadata Fields
Here are the available metadata options you can use:
| Option | Type / Default | Description |
|---|---|---|
id | String | Sets the HTML id for the <figure> tag. If not provided, a 10-character hash of the diagram code is used. This ID also defines the name of the output image files. |
alt | String | Highly recommended. Sets the alt text for the <img> tags. Crucial for accessibility — describe the diagram meaningfully (under 160 characters). |
caption | String | Adds a <figcaption> element below the diagram. |
width | String | Sets the width attribute on the <img> tags (e.g., 600px). Useful when rendered images are too large on desktop. |
prerender | false | If set to false, this plugin will skip this diagram entirely, leaving it for the client-side @docusaurus/theme-mermaid to render. |
descriptionId | String | Advanced accessibility feature linking the figure to an external description via aria-describedby. Use the id of an existing HTML element that contains the description text (for WCAG compliance). |
Styling Guide
The diagrams come without any default styling, so you can style them to fit your site's design.
.static-mermaid-figure: The main<figure>wrapper..mermaid-light: The<img>tag for the light theme..mermaid-dark: The<img>tag for the dark theme.figcaption: The caption element.
SCSS Styling Example
Here is an advanced example (using SCSS) to add a background, border-radius, and custom-numbered counters to your figures.
.static-mermaid-figure {
// Add a counter for each figure
counter-increment: figurecounter;
margin: 2rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem; // Space between image and caption
// Style the image tags
& > img {
max-width: 100%;
max-height: 100%;
padding: 1.5rem;
background-color: var(--ifm-card-background-color);
border-radius: 0.75rem;
border: 1px solid var(--ifm-color-emphasis-300);
}
// Style the caption
& > figcaption {
margin-top: 0.25rem;
font-size: 0.9rem;
color: var(--ifm-font-color-secondary);
font-style: italic;
text-align: center;
// Example of a custom "Figure 1.1: " counter
&::before {
content: 'Figure ' counter(h2counter) '.' counter(figurecounter) ': ';
font-weight: 600;
color: var(--ifm-font-color-base);
font-style: normal;
}
}
}
License
This plugin is released under the MIT License. You are free to use, modify, and distribute it as you see fit.
Contributing
Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.