fix(dev-hub): render mermaid diagrams at build time#3728
Conversation
The previous setup registered remarkMdxMermaid (a remark plugin) in the rehype plugins array and never registered a <Mermaid /> MDX component, so ```mermaid fences fell through to Shiki and rendered as plain syntax-highlighted code blocks (e.g. the TON pull-integration page). Replace it with rehype-mermaid using the inline-svg strategy, ordered before rehypeCode so the block is consumed before Shiki touches it. Diagrams are now rendered to inline SVG at build time via Playwright, with zero client-side mermaid runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
5 Skipped Deployments
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous attempt used rehype-mermaid for build-time SVG generation,
but Vercel's Amazon Linux 2023 build container is missing libnspr4 (and
other Chromium shared libs) and we can't sudo apt-get install on Vercel.
The build failed with:
chrome-headless-shell: error while loading shared libraries:
libnspr4.so: cannot open shared object file
Pivot to Fumadocs' canonical client-side flow:
- Move remarkMdxMermaid from rehypePlugins to remarkPlugins (it's a
remark plugin — the previous slot was a no-op).
- Add a small client <Mermaid /> component that lazy-imports mermaid
and renders into a div via dangerouslySetInnerHTML. Theme follows
next-themes resolvedTheme.
- Register <Mermaid /> in mdx-components.tsx.
- Drop playwright and rehype-mermaid; add mermaid as a direct dep.
- Revert the build script.
Mermaid is dynamically imported, so doc pages without diagrams pay no
bundle cost; pages with diagrams lazy-load mermaid (~250KB gz) after
hydration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous approach pulled in mermaid (~250KB gz client bundle) plus
40+ transitive deps for 7 sequence diagrams across the docs.
Replace with a ~15-line inline remark plugin in source.config.ts
that rewrites ```mermaid fenced blocks into:
<img src="https://mermaid.ink/svg/{base64}" alt="..." />
mermaid.ink is a free public service that renders mermaid source
to SVG. The diagrams ship as plain <img> tags — zero npm deps,
zero client-side JS, no Playwright, no Chromium.
Tradeoff: external service dependency. If mermaid.ink is ever
unavailable the images 404, but the page still renders.
Removes mermaid from dependencies, deletes the client Mermaid
component, and reverts the mdx-components registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous remark-plugin approach was not producing img tags through the fumadocs-mdx pipeline (still under investigation, possibly an MDX node-type or plugin-order interaction). Since the TON page is the only file in the entire repo using ```mermaid fences, drop the plugin entirely and inline the 7 sequence diagrams directly as <img src="https://mermaid.ink/svg/{base64}"> tags in the MDX source. All 7 generated URLs verified returning 200 image/svg+xml from mermaid.ink. Zero npm dependency footprint, zero runtime cost. If new docs need diagrams later, run the same base64-encode pattern or revisit the remark-plugin approach. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🚩 Assets in documentation should live in public/images per AGENTS.md
The AGENTS.md states: "assets belong in public/images". The new <img> tags reference external mermaid.ink URLs rather than local assets. While these are dynamically-rendered SVGs (not traditional image assets), the spirit of the guideline suggests documentation images should be hosted locally. Storing pre-rendered SVGs in public/images/ would align with this convention and also eliminate the external service dependency noted in ANALYSIS-0001. This is a grey area since the mermaid.ink URLs are essentially encoded diagram definitions, not static assets committed to the repo.
Was this helpful? React with 👍 or 👎 to provide feedback.
| note right of P: Pyth Contract validates update data and <br/> sends prices with payload to EVAA Master contract | ||
| note over M: EVAA Master validates sender <br/> parses payload and <br/> processes the transaction | ||
| ``` | ||
| <img src="https://mermaid.ink/svg/c2VxdWVuY2VEaWFncmFtCiAgYXV0b251bWJlcgogIHBhcnRpY2lwYW50IFUgYXMgVXNlcgogIHBhcnRpY2lwYW50IFAgYXMgUHl0aCBDb250cmFjdAogIHBhcnRpY2lwYW50IE0gYXMgRVZBQSBNYXN0ZXIKCiAgbm90ZSBvdmVyIE06IG1hc3Rlci5mYzoxMjEg4oCUIHJlY2VpdmVkIGZyb20gUHl0aCAob3AgMHg1KQogIFUtPj5QOiBvcCAweDUgcGFyc2VfcHJpY2VfZmVlZF91cGRhdGVzIChwcmljZSBmZWVkcyArIHVwZGF0ZSBkYXRhKTxici8+cGF5bG9hZCAob3AgMHgzIGxpcXVpZGF0ZV9tYXN0ZXIgfCAweDQgc3VwcGx5X3dpdGhkcmF3X21hc3RlcikKICBub3RlIHJpZ2h0IG9mIFU6IHNlbmQgcmVxdWVzdCB0byBQeXRoIENvbnRyYWN0IHdpdGggdXBkYXRlIGRhdGEgYW5kIG9wZXJhdGlvbiBwYXlsb2FkPGJyLz5kZXN0aW5hdGlvbiBhZGRyZXNzIGlzIEVWQUEgTWFzdGVyIGNvbnRyYWN0CiAgUC0tPj5NOiBvcCAweDUgcGFyc2VfcHJpY2VfZmVlZF91cGRhdGVzIChwcmljZSBmZWVkcyArIHByaWNlcyk8YnIvPnBheWxvYWQgKG9wIDB4MyBsaXF1aWRhdGVfbWFzdGVyIHwgMHg0IHN1cHBseV93aXRoZHJhd19tYXN0ZXIpCiAgbm90ZSByaWdodCBvZiBQOiBQeXRoIENvbnRyYWN0IHZhbGlkYXRlcyB1cGRhdGUgZGF0YSBhbmQgPGJyLz4gc2VuZHMgcHJpY2VzIHdpdGggcGF5bG9hZCB0byBFVkFBIE1hc3RlciBjb250cmFjdAogIG5vdGUgb3ZlciBNOiBFVkFBIE1hc3RlciB2YWxpZGF0ZXMgc2VuZGVyIDxici8+IHBhcnNlcyBwYXlsb2FkIGFuZCA8YnIvPiBwcm9jZXNzZXMgdGhlIHRyYW5zYWN0aW9u" alt="Mermaid diagram" style={{ maxWidth: "100%", height: "auto", display: "block", margin: "1.5rem auto" }} /> |
There was a problem hiding this comment.
📝 Info: External service dependency for diagram rendering
The approach delegates all mermaid rendering to mermaid.ink, an external third-party service. If mermaid.ink goes down or rate-limits requests, diagrams will appear as broken images to users. The previous approach (remarkMdxMermaid) rendered diagrams at build time without external dependencies. However, since the <img> tags are rendered client-side (the browser fetches the images), this doesn't block builds — it only affects the user's viewing experience. The commit history (apps/developer-hub/source.config.ts) shows multiple iterations trying to get mermaid rendering working, suggesting the fumadocs-core plugin had compatibility issues that motivated this approach.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ab1ce8492
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| note right of P: Pyth Contract validates update data and <br/> sends prices with payload to EVAA Master contract | ||
| note over M: EVAA Master validates sender <br/> parses payload and <br/> processes the transaction | ||
| ``` | ||
| <img src="https://mermaid.ink/svg/c2VxdWVuY2VEaWFncmFtCiAgYXV0b251bWJlcgogIHBhcnRpY2lwYW50IFUgYXMgVXNlcgogIHBhcnRpY2lwYW50IFAgYXMgUHl0aCBDb250cmFjdAogIHBhcnRpY2lwYW50IE0gYXMgRVZBQSBNYXN0ZXIKCiAgbm90ZSBvdmVyIE06IG1hc3Rlci5mYzoxMjEg4oCUIHJlY2VpdmVkIGZyb20gUHl0aCAob3AgMHg1KQogIFUtPj5QOiBvcCAweDUgcGFyc2VfcHJpY2VfZmVlZF91cGRhdGVzIChwcmljZSBmZWVkcyArIHVwZGF0ZSBkYXRhKTxici8+cGF5bG9hZCAob3AgMHgzIGxpcXVpZGF0ZV9tYXN0ZXIgfCAweDQgc3VwcGx5X3dpdGhkcmF3X21hc3RlcikKICBub3RlIHJpZ2h0IG9mIFU6IHNlbmQgcmVxdWVzdCB0byBQeXRoIENvbnRyYWN0IHdpdGggdXBkYXRlIGRhdGEgYW5kIG9wZXJhdGlvbiBwYXlsb2FkPGJyLz5kZXN0aW5hdGlvbiBhZGRyZXNzIGlzIEVWQUEgTWFzdGVyIGNvbnRyYWN0CiAgUC0tPj5NOiBvcCAweDUgcGFyc2VfcHJpY2VfZmVlZF91cGRhdGVzIChwcmljZSBmZWVkcyArIHByaWNlcyk8YnIvPnBheWxvYWQgKG9wIDB4MyBsaXF1aWRhdGVfbWFzdGVyIHwgMHg0IHN1cHBseV93aXRoZHJhd19tYXN0ZXIpCiAgbm90ZSByaWdodCBvZiBQOiBQeXRoIENvbnRyYWN0IHZhbGlkYXRlcyB1cGRhdGUgZGF0YSBhbmQgPGJyLz4gc2VuZHMgcHJpY2VzIHdpdGggcGF5bG9hZCB0byBFVkFBIE1hc3RlciBjb250cmFjdAogIG5vdGUgb3ZlciBNOiBFVkFBIE1hc3RlciB2YWxpZGF0ZXMgc2VuZGVyIDxici8+IHBhcnNlcyBwYXlsb2FkIGFuZCA8YnIvPiBwcm9jZXNzZXMgdGhlIHRyYW5zYWN0aW9u" alt="Mermaid diagram" style={{ maxWidth: "100%", height: "auto", display: "block", margin: "1.5rem auto" }} /> |
There was a problem hiding this comment.
Avoid hotlinking critical diagrams to mermaid.ink
Replacing the inline mermaid source with <img src="https://mermaid.ink/..."> makes this documentation page depend on a third-party runtime service for core content. In environments where mermaid.ink is blocked, rate-limited, or temporarily unavailable, readers will see broken images instead of the flow diagrams, which is a regression from self-contained docs content. Please keep diagrams in-repo (mermaid source rendered at build time, or checked-in SVG assets) so page correctness does not rely on external availability.
Useful? React with 👍 / 👎.
…ponent Root cause of the earlier "plugin didn't fire" — it did fire. Verified by running through @mdx-js/mdx in isolation: the ```mermaid block was correctly transformed into an mdxJsxFlowElement with name="img". The actual failure was downstream: fumadocs-ui's defaultMdxComponents maps the lowercase `img` JSX element to `next/image`, which requires `width` and `height` props. My plugin output had only `src` and `alt`, so next/image silently failed to render anything. Fix: have the plugin emit a uppercase component name (<MermaidDiagram>) which MDX resolves through the `components` prop. Register MermaidDiagram in mdx-components.tsx as a plain <img>, bypassing next/image entirely. Now any future ```mermaid fence anywhere in the docs auto-renders. Zero npm dependencies. Diagrams ship as inline <img> tags pointing at mermaid.ink. The 7 TON page diagrams revert from hand-inlined imgs back to the more readable ```mermaid source fences. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standard base64 can emit `/` and `+` which are unsafe in URL paths. A `/` would split the mermaid.ink path (e.g. /svg/abc/def) and cause a 404. The current TON diagrams happen not to hit it, but any future diagram whose source contains `?` (or other characters that base64 maps to `/`) would silently break. base64url (RFC 4648 §5) replaces `+` -> `-` and `/` -> `_`, so the encoded string is safe to drop directly into a URL path. Verified mermaid.ink returns 200 for the base64url form. Spotted by Devin's PR review on #3728. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| const encoded = Buffer.from(source, "utf8").toString("base64url"); | ||
| node.type = "mdxJsxFlowElement"; | ||
| node.name = "MermaidDiagram"; | ||
| node.attributes = [ | ||
| { | ||
| type: "mdxJsxAttribute", | ||
| name: "src", | ||
| value: `https://mermaid.ink/svg/${encoded}`, |
There was a problem hiding this comment.
🚩 mermaid.ink encoding format: base64url without prefix
The plugin uses Buffer.from(source, 'utf8').toString('base64url') and places the result directly in the URL path (source.config.ts:19,26). The standard mermaid.ink encoding uses either pako-compressed JSON or a base64: prefixed format. Raw base64url without a prefix would normally cause mermaid.ink to attempt pako decompression, which would fail on raw UTF-8 text. However, the commit message (9f45b7d) explicitly states 'Verified mermaid.ink returns 200 for the base64url form', so the developer confirmed this works empirically. This may indicate mermaid.ink has updated its fallback behavior. Worth noting that if mermaid.ink changes its API in the future, this could break — there's an implicit dependency on undocumented behavior.
Was this helpful? React with 👍 or 👎 to provide feedback.
| export const MermaidDiagram = ({ | ||
| src, | ||
| alt = "Mermaid diagram", | ||
| className, | ||
| }: MermaidDiagramProps) => ( | ||
| // eslint-disable-next-line @next/next/no-img-element | ||
| <img src={src} alt={alt} className={clsx(styles.diagram, className)} /> | ||
| ); |
There was a problem hiding this comment.
🚩 Dark mode compatibility for mermaid.ink SVGs
The MermaidDiagram component renders a plain <img> tag (src/components/MermaidDiagram/index.tsx:17). mermaid.ink renders SVGs using mermaid's default (light) theme — dark text on a light/transparent background. If the developer hub supports dark mode (it uses next-themes per package.json), these diagrams may be hard to read or visually jarring in dark mode since the SVG colors won't adapt. The old remarkMdxMermaid plugin may have had similar limitations, but it's worth considering adding ?theme=dark support or CSS filter: invert() for dark mode.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
The TON pull-integration page (and any other doc using
```mermaidfences) was rendering the diagram source as a plain Shiki-highlighted code block instead of an actual diagram. Example: https://docs.pyth.network/price-feeds/core/use-real-time-data/pull-integration/tonRoot cause. source.config.ts registered
remarkMdxMermaid(a remark plugin) inside the rehype plugins array, so it never ran in any useful way. Even if it had, there was no<Mermaid />MDX component registered to render the output. Net effect: fences fell through to Shiki and rendered as syntax-highlighted text.Fix. Replace the broken
remarkMdxMermaidregistration withrehype-mermaidusing theinline-svgstrategy, and order it beforerehypeCodeso Shiki doesn't grab the block first. Diagrams are now rendered to inline SVG at build time via Playwright — zero client-side mermaid runtime, perfect SEO, works without JS.Changes
remarkMdxMermaidforrehype-mermaidwithstrategy: "inline-svg", reorder pipeline.rehype-mermaidandplaywright; prependplaywright install chromiumto thebuildandbuild:analyzescripts so CI has the Chromium binary.Build cost
libnss3.so,libgbm.so), the build will fail loudly duringnext buildand we'll need a Vercel install hook. Dropped--with-depsfromplaywright installbecause it requiressudo apt-get.Test plan
pnpm installpnpm --filter @pythnetwork/developer-hub start:devand open/price-feeds/core/use-real-time-data/pull-integration/ton— confirm the 7 sequence diagrams render as SVG, not code blocks.pnpm --filter @pythnetwork/developer-hub buildsucceeds locally.🤖 Generated with Claude Code