# How MarkdownBody in pi-web Renders Markdown with Mermaid Diagrams and KaTeX Math

> Discover how pi-web's MarkdownBody uses remark and rehype plugins to render Markdown with Mermaid diagrams and KaTeX math. Learn about its robust pipeline for dynamic content.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-07-31

---

**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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/markdown.ts) as arrays named `markdownRemarkPlugins` and `markdownRehypePlugins`. **Remark plugins** handle the markdown-to-AST transformation:

- **`remark-gfm`** enables GitHub-flavored markdown features including tables, task lists, and strikethrough.
- **`remark-math`** detects mathematical notation wrapped in `\(...\)` for inline math and `$$…$$` for display blocks.

**Rehype plugins** transform the HTML AST:

- **`rehype-raw`** permits raw HTML inside markdown, allowing complex nested structures.
- **`rehype-sanitize`** applies a custom schema defined in [`lib/markdown.ts`](https://github.com/agegr/pi-web/blob/main/lib/markdown.ts) that whitelists `code` element classes for syntax highlighting while stripping unsafe tags and attributes.
- **`rehype-katex`** converts 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`](https://github.com/agegr/pi-web/blob/main/katex.min.css)) is globally imported in [`app/layout.tsx`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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:

```tsx
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.tsx`](https://github.com/agegr/pi-web/blob/main/components/MarkdownBody.tsx) orchestrating `ReactMarkdown` with remark plugins (`remark-gfm`, `remark-math`) and rehype plugins (`rehype-raw`, `rehype-sanitize`, `rehype-katex`).
- **KaTeX math** is rendered via `rehype-katex` with global styles provided in [`app/layout.tsx`](https://github.com/agegr/pi-web/blob/main/app/layout.tsx), supporting both inline and display notation.
- **Mermaid diagrams** are handled by [`components/MermaidBlock.tsx`](https://github.com/agegr/pi-web/blob/main/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.ts`](https://github.com/agegr/pi-web/blob/main/lib/markdown.ts) that 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/app/layout.tsx) includes dark mode variants, while [`MermaidBlock.tsx`](https://github.com/agegr/pi-web/blob/main/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.