Implement a Comprehensive Mermaid Chart Rendering System in Astro
Goals:
Create a flexible, component-based Mermaid chart rendering system for Astro that enhances the default markdown code blocks with the following features:
- Breaks Out of the Article column allowing mermaid charts to CENTER and span the full width of the page.
- Extended Styling through custom a custom CSS file that extends the default styling
- Expand and Collapse functionality for Mermaid charts, enabling the viewer to expand the chart to Full Page, taking up 100% of the viewport width and height.
Technical Requirements
CONSTRAINTS:
- Do NOT break the current rendering pipeline and Markdown HTML output.
- Do NOT make assumptions about any "library" or additional packages that we may or may not have. REVIEW OUR DEPENDENCIES at
package.json
. - Minimize additional packages and dependencies. If there is a way to do it without a popular dependency, do it.
- Take things STEP BY STEP. Do NOT try to write all changes at once across multiple files.
- Follow all other guidelines and conventions as laid out in various reminders, rulesets, and memories.
Component Architecture
Use the current rendering pipeline and components:
site/src/layouts/OneArticle.astro
layout.site/src/components/articles/OneArticleOnPage.astro
component. <-- most of the transformation logic is in here.site/src/components/articles/AstroMarkdown.astro
component. <-- most of the markdown rendering logic is in here.site/astro.config.mjs
configuration file.
New Components
site/src/components/articles/MermaidChart.astro
component.
Technical Perspectives:
- Mermaid SVGs are rendered independently of the main Markdown content.
- This means Mermaid diagrams are always grouped together at the top (or wherever the block is placed), not inline where the codeblock appeared in the original Markdown.
- The rest of the Markdown is rendered using your normal MDAST → Astro component pipeline, with codeblocks rendered as.
- rehypeMermaid only works at the HAST (HTML AST) stage.
- It expects to receive HAST nodes, not MDAST nodes.
- If you want to replace Mermaid codeblocks inline, you need to let the rehype pipeline run, but also preserve the mapping between original codeblock position and the generated SVG.
- You cannot simply “catch” Mermaid codeblocks in the MDAST and replace them with a component, because the diagram SVG is not available until after rehypeMermaid runs.
- If you swap in acomponent at the MDAST stage, you'd have to re-render the diagram on the client (using Mermaid.js in the browser), or re-run the mermaid rendering logic in your component.
Implementation Details
Hybrid Approach
At the MDAST stage, tag Mermaid codeblocks for special handling (e.g., add a custom property).
In the HAST pipeline, after rehypeMermaid, walk the HAST and replace placeholder nodes with the generated SVGs.
Pros: Flexible, can support fallback to client-side rendering if server-side fails.
id Approach: Tag and Replace
Step 1: Tag Mermaid Codeblocks in MDAST
During the MDAST (Markdown AST) processing stage, traverse the AST.
For each code node with lang: "mermaid", add a custom property (e.g., node.isMermaid = true or node.data = { ...node.data, isMermaid: true }).
Optionally, replace the codeblock with a placeholder node (e.g., a custom mermaidPlaceholder type, or a div with a unique ID).
Example:
js
CopyInsert
visit(tree, 'code', node => {
if (node.lang === 'mermaid') {
node.data = { ...(node.data || {}), isMermaid: true, id: generateUniqueId() };
}
});
Step 2: Pass Through to HAST and rehypeMermaid
Continue the pipeline:
MDAST → remarkRehype → HAST
rehypeMermaid will convert Mermaid codeblocks to diagrams, but only if they are still codeblocks at this stage.
Step 3: Replace Placeholders with SVGs in HAST
After rehypeMermaid runs, walk the HAST tree:
For each node that matches your placeholder (e.g., a div with a unique ID, or a node with a matching data property), replace it with the corresponding SVG node generated by rehypeMermaid.
This preserves the inline position of each Mermaid diagram.
Example (pseudo-code):
js
CopyInsert
visit(hast, 'element', node => {
if (node.properties && node.properties['data-mermaid-id']) {
// Find the SVG generated for this ID
const svg = findSvgForId(node.properties['data-mermaid-id']);
if (svg) {
Object.assign(node, svg);
}
}
});
Step 4: Fallback to Client-Side Rendering (Optional)
If the SVG cannot be generated (e.g., server-side error), you can:
Leave the placeholder in the output.
Use a component to render the diagram client-side as a fallback.
Pros of This Approach
Inline diagrams: Mermaid SVGs appear exactly where the original codeblock was.
Server-rendered by default: Good for SEO, print, and static export.
Flexible: You can add debug info, fallback to client-side rendering, or add custom UI (expand/collapse, error states).
Implementation Notes
You’ll need to maintain a mapping between codeblocks (by unique ID or order) and their rendered SVGs.
Use robust AST traversal libraries (unist-util-visit, hast-util-visit) to manipulate trees.
Comment every transformation step and maintain DRY, single-source-of-truth logic for codeblock handling.
Technical Implementation Plan: Inline Mermaid Rendering (Hybrid Approach)
Goal: Render Mermaid diagrams inline, at the exact position of their original codeblock, using a robust, debuggable, and DRY pipeline. Support server-rendered SVGs by default, with client-side fallback if needed.
1. Tag Mermaid Codeblocks in MDAST
- Traverse the Markdown AST (MDAST) after parsing.
- For each
code
node withlang: "mermaid"
, add a unique identifier and a custom property:jsvisit(tree, 'code', node => { if (node.lang === 'mermaid') { node.data = { ...(node.data || {}), isMermaid: true, mermaidId: generateUniqueId() }; } });
- Optionally, replace the codeblock node with a custom placeholder node (e.g.,
type: 'mermaidPlaceholder'
).
2. Convert to HAST and Run rehypeMermaid
- Pass the tagged tree through
remarkRehype
to convert to HAST (HTML AST). - Run
rehypeMermaid
to convert Mermaid codeblocks to SVGs. - Ensure that the unique identifier or placeholder is preserved in the resulting HAST nodes.
3. Replace Placeholders with SVGs in HAST
- After
rehypeMermaid
, traverse the HAST tree:- For each placeholder or node with the custom identifier, replace it with the corresponding SVG generated by rehypeMermaid.
- Maintain a mapping between codeblock IDs and SVGs (by order or explicit ID).
- Example (pseudo-code):js
visit(hast, 'element', node => { if (node.properties && node.properties['data-mermaid-id']) { const svg = findSvgForId(node.properties['data-mermaid-id']); if (svg) Object.assign(node, svg); } });
4. Fallback to Client-Side Rendering (Optional)
- If a diagram fails to render server-side, leave the placeholder in the output.
- Use a
<MermaidChart code={...} />
component to render the diagram client-side as a fallback.
5. Debugging and Observability
- Add debug output at each stage:
- Show the tagged MDAST, the HAST before and after SVG injection, and the final HTML.
- Clearly comment all transformation steps and mappings.
- Ensure toggling debug mode is simple and non-intrusive.
6. DRY and Maintainability
- Centralize all codeblock tagging, mapping, and replacement logic in utility functions or plugins.
- Document the full pipeline and all custom node properties.
This plan ensures:
- Diagrams render inline, not grouped or detached.
- Server-rendered SVGs by default, with a robust fallback.
- Debuggable, maintainable, and DRY code throughout the pipeline.
How to Identify and Extract Mermaid Codeblocks in the Markdown AST
1. Markdown Codeblock Structure
- Mermaid charts are written as fenced code blocks with the language identifier
mermaid
:text```mermaid graph TD; A-->B; B-->C;
text
2. How This Appears in the Markdown AST
- Most Markdown parsers (remark, mdast, unified) parse code blocks into AST nodes of type
code
. - Mermaid blocks are identified by:
type: "code"
lang: "mermaid"
value
: the Mermaid code as a string.
Example node:
json
{
"type": "code",
"lang": "mermaid",
"value": "graph TD;\nA-->B;\nB-->C;"
}
3. Extraction Approach
- Traverse the AST (using a visitor pattern or loop).
- For each node:
- If
node.type === "code" && node.lang === "mermaid"
, extractnode.value
.
4. Sample AST Traversal (remark/unist-util-visit)
js
import { visit } from 'unist-util-visit';
/**
* Extracts all Mermaid codeblocks from a Markdown AST.
* @param {object} tree - The Markdown AST.
* @returns {Array<string>} Array of Mermaid code strings.
*/
function extractMermaidCodeblocks(tree) {
const mermaidBlocks = [];
visit(tree, 'code', (node) => {
if (node.lang === 'mermaid') {
mermaidBlocks.push(node.value);
}
});
return mermaidBlocks;
}
5. Integration
- In your Astro pipeline (e.g. AstroMarkdown.astro or a remark/rehype plugin):
- Traverse the AST as above.
- Replace each Mermaid codeblock node with your custom
<MermaidChart code={node.value} />
component.
Troubleshooting:
hat Needs to Change
Stop using compiledContent as a shortcut for the whole markdown render.
Always walk the AST (content={transformedMdast}) in AstroMarkdown.astro.
When a code block is detected:
If it’s a mermaid block (e.g. node.data.isMermaid and node.data.mermaidId), render the SVG for that ID.
Else, use .
How to get the SVGs?
The SVGs are generated and inlined by rehypeMermaidInline in the HAST, but you need a way to pass a mapping of mermaidId → SVG to the AST walker.
This mapping must be extracted during processing in OneArticle.astro and passed as a prop to AstroMarkdown.astro.
Action Plan
In OneArticle.astro:
After running the processor and before calling <Component ...>, extract the mapping { [mermaidId]: svgString } from the HAST (after rehypeMermaidInline).
Pass this as a prop: mermaidSvgs={svgMap}.
In AstroMarkdown.astro:
Accept a mermaidSvgs prop.
When rendering a code block node, if it’s a mermaid block, render the SVG from mermaidSvgs[node.data.mermaidId] as raw HTML.
Otherwise, use .
Next Steps
I will implement these changes in both files, with full comments and respecting your code style and architectural rules.
Summary:
You will get a pipeline that always walks the AST, renders all code blocks through your custom logic, and inlines mermaid SVGs at the right place—no more short-circuiting to compiledContent.
References:
Success Criteria:
- 1. The current DEBUG architecture is working, and we can ENABLE the DEBUG mode. This will allow us to examine the markdown transformation pipeline along each stage.
- 2. The current Mermaid rendering code is rendering for MULTIPLE mermaid codeblocks in the same markdown file.
- 3. The Mermaid code is refactored into a new
MermaidChart.astro
component. - 4. The specification
content/specs/Filesystem-Observer-for-Consistent-Metadata-in-Markdown-files.md
is rendered through the page:site/src/pages/vibe-with/[collection]/[...slug].astro