How to Use Mermaid.js in Next.js (App Router & Pages Router)
Step-by-step guide to integrating Mermaid.js diagrams in Next.js. Covers dynamic imports, SSR pitfalls, custom hooks, and rendering flowcharts, sequence diagrams, and ER diagrams in your Next.js app.
# How to Use Mermaid.js in Next.js (App Router & Pages Router)
Mermaid.js is one of the most popular libraries for rendering text-based diagrams — flowcharts, sequence diagrams, ER diagrams, class diagrams, and more. If you're building a Next.js app and want to embed live diagrams, you'll hit a few SSR-specific gotchas. This guide walks you through everything, from installation to a production-ready component.
---
Why Mermaid.js + Next.js Is Tricky
Mermaid.js is a browser-only library. It manipulates the DOM directly during rendering. Next.js runs code on the server first (SSR / RSC), which means:
- window and document don't exist on the server
- Importing Mermaid at the module level throws ReferenceError: window is not defined
- You must use dynamic imports inside useEffect or with next/dynamic
Once you understand this, the integration is straightforward.
---
Installation
npm install mermaidNo extra type package is needed — Mermaid ships its own TypeScript definitions.
---
Approach 1: Dynamic Import with next/dynamic (Pages Router)
This is the simplest approach for the Pages Router. Create a wrapper component and load it client-side only.
components/MermaidDiagram.tsx
import { useEffect, useRef } from "react";
interface MermaidDiagramProps {
chart: string;
}
export default function MermaidDiagram({ chart }: MermaidDiagramProps) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let cancelled = false;
async function render() {
const mermaid = (await import("mermaid")).default;
mermaid.initialize({
startOnLoad: false,
theme: "default",
securityLevel: "loose",
});
if (ref.current && !cancelled) {
ref.current.innerHTML = "";
const id = "mermaid-" + Math.random().toString(36).slice(2);
const { svg } = await mermaid.render(id, chart);
if (ref.current && !cancelled) {
ref.current.innerHTML = svg;
}
}
}
render();
return () => { cancelled = true; };
}, [chart]);
return <div ref={ref} className="mermaid-container" />;
}In your page (Pages Router):
import dynamic from "next/dynamic";
const MermaidDiagram = dynamic(() => import("../components/MermaidDiagram"), {
ssr: false,
loading: () => <p>Loading diagram...</p>,
});
export default function DocsPage() {
const chart = `flowchart TD
A[User Request] --> B{Auth Check}
B -- Pass --> C[Load Data]
B -- Fail --> D[401 Error]
C --> E[Return Response]`;
return (
<main>
<h1>Architecture Overview</h1>
<MermaidDiagram chart={chart} />
</main>
);
}---
Approach 2: App Router with "use client" Directive
In the Next.js App Router, server components are the default. Mark your Mermaid component as a client component with "use client" at the top — no next/dynamic wrapper needed.
"use client";
import { useEffect, useRef, useState } from "react";
export default function MermaidDiagram({ chart }: { chart: string }) {
const ref = useRef<HTMLDivElement>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!ref.current) return;
let active = true;
(async () => {
try {
const mermaid = (await import("mermaid")).default;
mermaid.initialize({ startOnLoad: false, theme: "neutral" });
const id = "mmd-" + Date.now();
const { svg } = await mermaid.render(id, chart);
if (active && ref.current) ref.current.innerHTML = svg;
} catch (e) {
if (active) setError(String(e));
}
})();
return () => { active = false; };
}, [chart]);
if (error) return <pre style={{ color: "red" }}>{error}</pre>;
return <div ref={ref} />;
}Use it in any server component page:
// app/docs/page.tsx (Server Component — fine to import a client child)
import MermaidDiagram from "@/components/MermaidDiagram";
const chart = `sequenceDiagram
Client->>API: POST /login
API->>DB: SELECT user
DB-->>API: user row
API-->>Client: 200 OK + JWT`;
export default function DocsPage() {
return (
<section>
<h2>Authentication Flow</h2>
<MermaidDiagram chart={chart} />
</section>
);
}---
Dark Mode Support
Pass the theme option to mermaid.initialize(). To respect the user's OS preference:
const prefersDark =
typeof window !== "undefined" &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
mermaid.initialize({
startOnLoad: false,
theme: prefersDark ? "dark" : "default",
});Available built-in themes: default, neutral, dark, forest, base.
---
Rendering Multiple Diagrams
Each call to mermaid.render() needs a unique element ID. Use a timestamp or UUID:
const uniqueId = "mermaid-" + Math.random().toString(36).slice(2, 9);
const { svg } = await mermaid.render(uniqueId, chart);If you reuse IDs across re-renders, Mermaid throws Element with id already exists.
---
Common Errors and Fixes
ReferenceError: window is not defined
- Cause: Mermaid imported at module level
- Fix: Move import inside useEffect or use dynamic import
Element with id already exists
- Cause: Same ID passed to mermaid.render() twice
- Fix: Generate a unique ID each render cycle
Blank output, no error
- Cause: ref.current is null when render runs
- Fix: Check the effect dependency array — ensure it runs after mount
SVG renders but styles are missing
- Cause: CSP blocking inline styles
- Fix: Add style-src 'unsafe-inline' to your Content-Security-Policy header
---
ER Diagram Example
erDiagram
USER {
int id PK
string email
string name
}
POST {
int id PK
string title
int userId FK
}
COMMENT {
int id PK
string body
int postId FK
}
USER ||--o{ POST : writes
POST ||--o{ COMMENT : hasPass this string directly to to render a live ER diagram in your Next.js database schema docs page.
---
Try It Without Installing Anything
If you just need to prototype a diagram, MermaidEditor.lol lets you write and preview Mermaid diagrams instantly in the browser — no setup required. Copy the output SVG directly into your Next.js project.
---
Summary
- Always import Mermaid dynamically or inside useEffect — never at the module level
- Use "use client" in App Router, or next/dynamic({ ssr: false }) in Pages Router
- Generate unique IDs for each mermaid.render() call
- Use the theme option to match your app's color scheme
- Catch render errors and surface them — malformed syntax throws instead of returning empty SVG
With this setup, you can embed fully rendered flowcharts, sequence diagrams, class diagrams, and any other Mermaid diagram type directly inside your Next.js application.