How the Mermaid Block Renderer Works in Dify Chat: A Deep Dive into the Flowchart Component

The Mermaid block renderer in Dify Chat detects fenced code blocks with the mermaid language identifier and routes them to a dedicated Flowchart component that lazily loads the Mermaid library, debounces rendering, and outputs SVG diagrams as base64-encoded images.

The Mermaid block renderer is a specialized subsystem within the Dify Chat markdown pipeline that converts text-based diagram syntax into interactive visualizations. According to the lexmin0412/dify-chat source code, this implementation separates detection logic from rendering logic across two main files, enabling efficient lazy loading and robust error handling for complex diagrams like flowcharts and sequence diagrams.

Detection and Routing in the Markdown Pipeline

The rendering process begins in packages/react-app/src/components/markdown-renderer/index.tsx, where the CodeBlock component acts as a router for fenced code blocks. When ReactMarkdown parses the document, it passes the language identifier and content to this component.

The detection logic is straightforward: if language === 'mermaid', the component returns <Flowchart PrimitiveCode={content} /> instead of a standard syntax-highlighted code block. This early exit point (around lines 40-42 in the source) ensures that only Mermaid-specific content triggers the heavier rendering pipeline, while other code blocks receive standard treatment.

// Simplified detection logic from index.tsx
if (language === 'mermaid') {
  return <Flowchart PrimitiveCode={content} />;
}
return <StandardCodeBlock language={language} content={content} />;

The Flowchart Component Architecture

The core rendering logic resides in packages/react-app/src/components/markdown-renderer/blocks/mermaid.tsx. This component manages the entire lifecycle of diagram generation, from library initialization to SVG finalization.

Lazy Loading and API Initialization

To minimize initial bundle size, the component imports the mermaid library dynamically using a lazy import pattern. The initialization stores mermaid.mermaidAPI in a module-level variable named mermaidAPI, but only executes in browser environments (typeof window !== 'undefined').

This approach ensures server-side rendering (SSR) compatibility while deferring the substantial Mermaid dependency until the first diagram appears in the chat interface.

// Module-level variable and initialization
let mermaidAPI: any;

useEffect(() => {
  if (typeof window !== 'undefined' && !mermaidAPI) {
    import('mermaid').then((mermaid) => {
      mermaidAPI = mermaid.mermaidAPI;
      mermaid.initialize({
        theme: 'neutral',
        look: look, // 'classic' | 'handDrawn'
        flowchart: { htmlLabels: true, useMaxWidth: true }
      });
    });
  }
}, [look]);

Debounced Rendering Pipeline

To prevent performance degradation during rapid content updates, the Mermaid block renderer implements a 300-millisecond debounce on the rendering effect. The useEffect hook watches the PrimitiveCode prop and schedules the actual render operation only after the user stops typing.

The internal renderFlowchart function (wrapped in useEffectEvent) calls mermaidAPI.render('flowchart', PrimitiveCode), receiving an SVG string that undergoes two transformations: cleanUpSvgCode sanitizes the markup, and svgToBase64 converts it to a data URL for safe embedding.

// Debounced rendering logic
useEffect(() => {
  if (!mermaidAPI) return;
  
  // Clear pending renders
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
  
  // Schedule new render
  timeoutRef.current = setTimeout(() => {
    renderFlowchart();
  }, 300);
}, [PrimitiveCode]);

const renderFlowchart = useEffectEvent(async () => {
  try {
    const { svg } = await mermaidAPI.render('flowchart', PrimitiveCode);
    const cleaned = cleanUpSvgCode(svg);
    setSvgCode(svgToBase64(cleaned));
    setLoading(false);
  } catch (err) {
    setErrMsg(err.message);
  }
});

Visual Style Configuration

The component exposes a Radio.Group control that allows users to toggle between classic and handDrawn visual styles. Selecting "手绘" (hand-drawn) triggers a re-initialization of the Mermaid API with updated theme parameters, causing the diagram to regenerate with sketch-like aesthetics while preserving the underlying graph structure.

<Radio.Group
  value={look}
  buttonStyle="solid"
  optionType="button"
  onChange={(e) => setLook(e.target.value as 'classic' | 'handDrawn')}
>
  <Radio value="classic">经典</Radio>
  <Radio value="handDrawn">手绘</Radio>
</Radio.Group>

Error Handling and User Experience

When mermaidAPI.render encounters invalid syntax, the component captures the exception and displays a user-friendly error state. The UI renders an ExclamationTriangleIcon alongside the error message, allowing users to correct their Mermaid syntax without breaking the chat flow.

During the brief 300ms debounce window and the subsequent SVG generation, a LoadingOutlined spinner provides visual feedback. Once the base64 data URL is ready, the component renders a standard <img> tag with the SVG content, wrapped in a click-to-preview container that supports zoom interactions for complex diagrams.

{errMsg && (
  <div className="px-[26px] py-4">
    <ExclamationTriangleIcon className="h-6 w-6 text-red-500" />
    <span>{errMsg}</span>
  </div>
)}

{svgCode && !loading && (
  <img 
    src={svgCode} 
    alt="Mermaid Diagram" 
    className="max-w-full"
  />
)}

Integration with the Markdown Ecosystem

The Mermaid block renderer coexists with other markdown extensions through the ReactMarkdown pipeline configured with remarkGfm and rehypeRaw. Because the CodeBlock component selectively intercepts only mermaid language blocks, standard features like tables, LaTeX equations, and ECharts visualizations continue to function normally alongside diagram content.

This architectural decision maintains the composability of the markdown renderer while adding specialized visual capabilities. The Flowchart component receives the raw Mermaid source as PrimitiveCode and operates as a black box, emitting only an image element that fits naturally into the surrounding document flow.

Summary

  • Detection occurs in packages/react-app/src/components/markdown-renderer/index.tsx, where the CodeBlock component routes mermaid language blocks to the specialized Flowchart renderer.
  • Lazy loading prevents SSR issues and reduces initial bundle size by importing the Mermaid library only when browser rendering is required.
  • Debounced rendering at 300ms prevents excessive re-renders during streaming chat responses, optimizing performance for real-time applications.
  • Dual theme support allows runtime switching between classic and hand-drawn aesthetics via the look configuration parameter.
  • Error boundaries capture syntax errors and display them inline without crashing the chat interface.

Frequently Asked Questions

How does Dify Chat handle invalid Mermaid syntax?

When the mermaidAPI.render method throws an exception due to syntax errors, the Flowchart component catches the error in the renderFlowchart function and stores the message in the errMsg state. The UI then displays an ExclamationTriangleIcon with the specific error description, allowing users to see what went wrong without breaking the chat interface.

What is the purpose of the 300ms debounce in the Mermaid renderer?

The 300-millisecond debounce prevents the Mermaid library from re-rendering diagrams on every keystroke during streaming responses or rapid editing. This optimization is crucial for maintaining smooth performance in chat applications where content updates frequently, as it waits for the user to pause typing before executing the expensive SVG generation process.

Can I customize the default visual style for all Mermaid diagrams in Dify Chat?

The current implementation exposes a look state with two options: classic and handDrawn. While the component initializes with classic as the default, the source code in mermaid.tsx shows that the mermaid.initialize call accepts theme parameters. Developers can modify the default initialization configuration in the source, though end-users can also toggle styles per-diagram using the built-in radio buttons.

Where does the actual SVG-to-image conversion happen in the codebase?

The conversion logic resides in the mermaid.tsx file within the packages/react-app/src/components/markdown-renderer/blocks/ directory. After calling mermaidAPI.render, the component passes the resulting SVG string through cleanUpSvgCode for sanitization, then feeds it into svgToBase64 to generate a base64-encoded data URL. This data URL is stored in the svgCode state and rendered as an <img> tag, ensuring safe content security policy compliance compared to raw HTML injection.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →