feat(mdx): add mermaid diagram support - #9107
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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-mermaidto the MDX rehype plugin chain (before Shiki) and addedmermaid/rehype-mermaiddependencies. - Introduced a new
MermaidMDX component that lazy-loads Mermaid on the client and re-renders on theme changes. - Updated the
MDXCodeBox(preoverride) 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.
| 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
4b6eca2 to
bc37755
Compare
|
Addressed the Copilot review on the Mermaid component (head
|
| 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'; |
There was a problem hiding this comment.
Doesn't this dramatically slow down builds since it requires initializing a whole browser instance?
There was a problem hiding this comment.
No — with strategy: 'pre-mermaid' no browser is ever started at build time. Verified in the dependency source:
createMermaidRenderer()only creates a lazybrowserPromise(browserPromise ||= getBrowser(...)inside the returned render function inmermaid-isomorphic) — nothing launches at plugin setup.rehype-mermaidnever calls that render function forpre-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).
There was a problem hiding this comment.
Can you do some benchmarks and compare building site previously and after? + adding an example storybook with mermaid content so we can render/test?
| [rehypeAutolinkHeadings, { behavior: 'wrap' }], | ||
| // Transforms ```mermaid code blocks into renderable diagrams; | ||
| // must run before Shiki so they are not highlighted as plain code | ||
| [rehypeMermaid, { strategy: 'pre-mermaid' }], |
There was a problem hiding this comment.
If we're using rehype-mermaid, do we need to add mermaid support on code boxes? Or what exactly this rehype plugin does?
| const { resolvedTheme } = useTheme(); | ||
| const reactId = useId().replace(/:/g, '-'); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
So we're doing mermaid rendering solely on the client-side? Is this what we'd like to have, @avivkeller?
BTW, @ashrees could you extract this effect into a dedicated hook, so we can unit test the Mermaid specific API within an unit testable hook?
There was a problem hiding this comment.
I saw some people made browserless versions of Mermaid, e.g.:
https://github.com/1jehuang/mermaid-rs-renderer
There was a problem hiding this comment.
I can write bindings for it?
There was a problem hiding this comment.
Looks like they already exist at https://www.npmjs.com/package/@mermanjs/web
Adds Mermaid diagram support to the MDX pipeline, as requested in #7540 (blessed there by @AugustinMauroy, with
rehype-mermaidsuggested by @flakey5).How it works
rehype-mermaid(strategypre-mermaid) is added to the rehype chain inapps/site/mdx/plugins.mjs, positioned before@node-core/rehype-shikiso```mermaidfenced blocks become<pre class="mermaid">instead of being syntax-highlighted as plain code.MDXCodeBox(thepreMDX override) detects themermaidclass and renders a newMermaidclient component instead of a code box.Mermaidcomponent lazy-loads themermaidlibrary on the client (keeping it out of the initial bundle) and re-renders on theme change vianext-themes(default/darkthemes).Testing
```mermaidblocks emit<pre class="mermaid">with the diagram source intact, while regular code blocks still go through Shiki highlighting.eslintandstylelintpass 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