How MarkdownBody in pi-web Renders Markdown with Mermaid Diagrams and KaTeX Math
MarkdownBody in the agegr/pi-web repository renders markdown through a React-Markdown pipeline that chains remark plugins for GitHub-flavored syntax and math parsing, rehype plugins for KaTeX rendering and HTML sanitization, and conditionally renders Mermaid diagrams via a lazy-loaded client-side component.
The MarkdownBody component in the open-source agegr/pi-web project transforms raw markdown strings into interactive, styled HTML. This implementation supports GitHub-flavored markdown, mathematical notation, and Mermaid diagrams through a carefully configured plugin architecture. Understanding this pipeline reveals how the application balances rich rendering capabilities with security-conscious content sanitization.
The React-Markdown Pipeline Architecture
At the core of the rendering logic, components/MarkdownBody.tsx receives raw markdown content and passes it through a normalization step before invoking the <ReactMarkdown> component. The component first calls normalizeDisplayMath from lib/markdown.ts to standardize LaTeX display delimiters, ensuring consistent math parsing downstream. This pre-processing step guarantees that display math blocks using $$…$$ delimiters are correctly handled before the remark plugins process the AST.
Normalizing LaTeX Display Delimiters
The normalizeDisplayMath function in lib/markdown.ts adjusts raw markdown strings to accommodate the expectations of the math parsing plugins. This normalization runs immediately before the content enters the React-Markdown component, preventing delimiter mismatches that could break KaTeX rendering. The function ensures that both inline math (\(...\)) and display math ($$…$$) are properly formatted for the subsequent remark-math plugin.
Configuring Remark and Rehype Plugins
The pipeline relies on a specific sequence of plugins exported from lib/markdown.ts as arrays named markdownRemarkPlugins and markdownRehypePlugins. Remark plugins handle the markdown-to-AST transformation:
remark-gfmenables GitHub-flavored markdown features including tables, task lists, and strikethrough.remark-mathdetects mathematical notation wrapped in\(...\)for inline math and$$…$$for display blocks.
Rehype plugins transform the HTML AST:
rehype-rawpermits raw HTML inside markdown, allowing complex nested structures.rehype-sanitizeapplies a custom schema defined inlib/markdown.tsthat whitelistscodeelement classes for syntax highlighting while stripping unsafe tags and attributes.rehype-katexconverts math nodes into rendered KaTeX HTML.
Rendering KaTeX Mathematics
Mathematical expressions are rendered server-side through the rehype-katex plugin, which transforms the math nodes detected by remark-math into static HTML. The KaTeX stylesheet (katex.min.css) is globally imported in app/layout.tsx, ensuring that math notation displays correctly across all routes without flash-of-unstyled-content issues. The styling automatically respects the application's theme settings, providing appropriate contrast in both light and dark modes.
Handling Mermaid Diagrams
When components/MarkdownBody.tsx encounters a fenced code block, it inspects the language identifier. For standard languages, it renders a generic <CodeBlock> component, but when the language is explicitly mermaid, it delegates rendering to the specialized <MermaidBlock> component. This conditional routing ensures that diagram syntax is never passed through the standard syntax highlighter, which would fail to parse the Mermaid-specific grammar.
The MermaidBlock Component
components/MermaidBlock.tsx implements a client-side-only rendering strategy for Mermaid diagrams. It lazily loads the mermaid library using dynamic imports, preventing the bundling of the diagramming engine in server-side code. Upon initialization, the component configures Mermaid with a theme option matching the current UI state (dark or default) retrieved via the useTheme hook. The diagram source is parsed client-side, and the resulting SVG is stored in component state for display.
Interactive Diagram Features
The Mermaid rendering implementation provides a dual-mode viewing experience. Users initially see a preview button overlaying the code, which toggles the rendered SVG diagram. Clicking the diagram opens a zoom-able dialog for detailed inspection. This interaction pattern keeps the interface lightweight while supporting complex visualizations like flowcharts, sequence diagrams, and entity-relationship diagrams.
Security and Sanitization
The pipeline prioritizes XSS prevention through rehype-sanitize configured with a custom schema in lib/markdown.ts. This schema explicitly whitelists code element class attributes to preserve syntax highlighting identifiers (like language-typescript) while removing potentially dangerous HTML tags and event handlers. By combining rehype-raw with strict sanitization, the system allows authors to embed raw HTML for advanced layouts without exposing the application to injection attacks.
Code Example
The following TypeScript example demonstrates how MarkdownBody processes content containing both KaTeX math and a Mermaid diagram:
import MarkdownBody from './components/MarkdownBody';
function DocumentViewer() {
return (
<MarkdownBody
cwd="/home/user/project"
onOpenFile={(path) => console.log('opening', path)}
>
{`# Physics Documentation
Inline math example: \\(E = mc^2\\) and a display equation:
$$
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
$$
System architecture:
\`\`\`mermaid
graph LR
A[Client Request] --> B{Load Balancer}
B --> C[Server A]
B --> D[Server B]
\`\`\`
`}
</MarkdownBody>
);
}
In this example, the remark-math plugin detects the LaTeX delimiters, rehype-katex renders the equations, and the mermaid block triggers the lazy-loaded <MermaidBlock> component.
Summary
- Markdown processing relies on
components/MarkdownBody.tsxorchestratingReactMarkdownwith remark plugins (remark-gfm,remark-math) and rehype plugins (rehype-raw,rehype-sanitize,rehype-katex). - KaTeX math is rendered via
rehype-katexwith global styles provided inapp/layout.tsx, supporting both inline and display notation. - Mermaid diagrams are handled by
components/MermaidBlock.tsx, which lazy-loads the library and renders SVG client-side with theme-aware configuration. - Security is enforced through a custom sanitization schema in
lib/markdown.tsthat permits syntax highlighting classes while blocking unsafe HTML.
Frequently Asked Questions
What plugins does MarkdownBody use for markdown processing?
MarkdownBody uses remark-gfm for GitHub-flavored markdown features and remark-math for parsing LaTeX math delimiters. On the rehype side, it applies rehype-raw for HTML support, rehype-sanitize for security, and rehype-katex for math rendering. These are exported as arrays from lib/markdown.ts and passed to the <ReactMarkdown> component.
How does pi-web prevent XSS when rendering raw HTML in markdown?
The application uses rehype-sanitize with a custom schema defined in lib/markdown.ts that whitelists specific attributes like class on code elements for syntax highlighting while stripping all other potentially dangerous tags and JavaScript event handlers. This sanitization runs after rehype-raw processes the HTML, ensuring unsafe content never reaches the DOM.
Is Mermaid rendered on the server or client side?
Mermaid diagrams are rendered exclusively on the client side. The MermaidBlock.tsx component dynamically imports the mermaid library only when needed, keeping the server bundle small and avoiding server-side rendering issues with browser-specific APIs that Mermaid requires for SVG generation.
How does the application support dark mode for math and diagrams?
Both KaTeX and Mermaid respect the application's theme via the useTheme hook. The KaTeX stylesheet in app/layout.tsx includes dark mode variants, while MermaidBlock.tsx explicitly sets Mermaid's theme option to dark or default based on the current UI state, ensuring diagrams and equations maintain proper contrast in all viewing modes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →