Skip to content

feat(mdx): add mermaid diagram support - #9107

Open
ashrees wants to merge 1 commit into
nodejs:mainfrom
ashrees:feat/mermaid-support
Open

feat(mdx): add mermaid diagram support#9107
ashrees wants to merge 1 commit into
nodejs:mainfrom
ashrees:feat/mermaid-support

Conversation

@ashrees

@ashrees ashrees commented Aug 14, 2026

Copy link
Copy Markdown

Adds Mermaid diagram support to the MDX pipeline, as requested in #7540 (blessed there by @AugustinMauroy, with rehype-mermaid suggested by @flakey5).

How it works

  • rehype-mermaid (strategy pre-mermaid) is added to the rehype chain in apps/site/mdx/plugins.mjs, positioned before @node-core/rehype-shiki so ```mermaid fenced blocks become <pre class="mermaid"> instead of being syntax-highlighted as plain code.
  • MDXCodeBox (the pre MDX override) detects the mermaid class and renders a new Mermaid client component instead of a code box.
  • The Mermaid component lazy-loads the mermaid library on the client (keeping it out of the initial bundle) and re-renders on theme change via next-themes (default / dark themes).
  • Invalid diagram sources fall back to rendering the raw source text.

Testing

  • Verified the rehype chain ordering in isolation: ```mermaid blocks emit <pre class="mermaid"> with the diagram source intact, while regular code blocks still go through Shiki highlighting.
  • eslint and stylelint pass on all changed files.

Happy to switch to a build-time strategy (inline-svg) if preferred — I chose client-side rendering to avoid adding a heavy build-time dependency (Playwright/Chromium).

Fixes #7540

@ashrees
ashrees requested a review from a team as a code owner August 14, 2026 20:19
Copilot AI lite review requested due to automatic review settings August 14, 2026 20:19
@ashrees
ashrees requested a review from a team as a code owner August 14, 2026 20:19
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nodejs-org Ready Ready Preview Aug 14, 2026 11:46pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Mermaid diagram rendering support to the site’s MDX pipeline by transforming ```mermaid fenced blocks into a renderable form during MDX compilation and then rendering them client-side at runtime.

Changes:

  • Added rehype-mermaid to the MDX rehype plugin chain (before Shiki) and added mermaid/rehype-mermaid dependencies.
  • Introduced a new Mermaid MDX component that lazy-loads Mermaid on the client and re-renders on theme changes.
  • Updated the MDXCodeBox (pre override) to route Mermaid blocks to the new Mermaid renderer instead of the standard code box.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pnpm-lock.yaml Locks new dependencies pulled in by mermaid and rehype-mermaid.
apps/site/package.json Adds mermaid and rehype-mermaid runtime dependencies.
apps/site/mdx/plugins.mjs Inserts rehype-mermaid before Shiki to avoid Mermaid blocks being highlighted as plain code.
apps/site/components/MDX/Mermaid/index.tsx New client component that loads Mermaid lazily and renders diagrams (with theme support).
apps/site/components/MDX/Mermaid/index.module.css Basic layout constraints for Mermaid-rendered SVG output.
apps/site/components/MDX/CodeBox/index.tsx Detects Mermaid <pre class="mermaid"> blocks and renders the Mermaid component instead of CodeBox.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +23 to +47
const renderDiagram = async () => {
// Mermaid is heavy, so we only load it on the client when needed
const { default: mermaid } = await import('mermaid');

mermaid.initialize({
startOnLoad: false,
theme: resolvedTheme === 'dark' ? 'dark' : 'default',
});

try {
const { svg } = await mermaid.render(
`mermaid-${reactId}`,
String(children).trim()
);

if (!cancelled && containerRef.current) {
containerRef.current.innerHTML = svg;
}
} catch {
// If the diagram source is invalid, fall back to showing the source
if (!cancelled && containerRef.current) {
containerRef.current.textContent = String(children);
}
}
};
Adds rehype-mermaid (pre-mermaid strategy) to the MDX rehype chain before Shiki, and renders the resulting pre.mermaid blocks with a client-side Mermaid component that follows the site's theme.

Fixes: nodejs#7540
@ashrees
ashrees force-pushed the feat/mermaid-support branch from 4b6eca2 to bc37755 Compare August 14, 2026 23:45
@ashrees

ashrees commented Aug 14, 2026

Copy link
Copy Markdown
Author

Addressed the Copilot review on the Mermaid component (head bc37755):

  • the dynamic import('mermaid') now sits inside the try block, so a failed chunk load falls back gracefully
  • securityLevel: 'strict' is set on mermaid.initialize
  • bindFunctions from mermaid.render() is invoked, enabling diagram interactions
  • the error fallback now renders the source inside a <pre> to preserve whitespace

Comment thread apps/site/mdx/plugins.mjs
import rehypeShikiji from '@node-core/rehype-shiki/plugin';
import remarkHeadings from '@vcarl/remark-headings';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeMermaid from 'rehype-mermaid';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this dramatically slow down builds since it requires initializing a whole browser instance?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No — with strategy: 'pre-mermaid' no browser is ever started at build time. Verified in the dependency source:

  • createMermaidRenderer() only creates a lazy browserPromise (browserPromise ||= getBrowser(...) inside the returned render function in mermaid-isomorphic) — nothing launches at plugin setup.
  • rehype-mermaid never calls that render function for pre-mermaid; it only rewrites the AST (<pre><code class="language-mermaid"><pre class="mermaid">). The package comments this exact path as "No need to start a browser in this case."
  • Playwright (peer dep) is only exercised by the inline-svg / img-* strategies.

So build cost is a single AST walk per document, and rendering happens client-side — with the mermaid library itself lazy-loaded via dynamic import(), so it stays out of the initial bundle too.

Happy to switch to inline-svg if the team prefers zero client-side JS — that's the trade-off (build-time Chromium vs. client rendering).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mermaid diagram support

3 participants