Skip to content
rodolfo.gg
Go back

Grafiken einfach einbinden: Mermaid.

CC BY-NC-ND 4.0
Rodolfo González González

Grafiken einfach einbinden: Mermaid.

Einleitung

Mermaid ist eine Auszeichnungssprache, mit der sich Diagramme deklarativ erstellen lassen, indem eine JavaScript-Bibliothek die Grafiken generiert.

Mermaid wurde von Knut Sveidqvist entwickelt, der sich inspirieren ließ, als er mit seinen Kindern Arielle, die Meerjungfrau (The Little Mermaid) sah. Ursprünglich als Werkzeug zur Diagrammerstellung aus Text mit Markdown-ähnlicher Syntax entwickelt, hat es sich zu einem vollständigen Ökosystem entwickelt, das in der Community weit verbreitet ist: Das Projekt zählt mittlerweile über 87.500 Sterne auf GitHub.

Mermaid lässt sich nativ in Systeme wie GitHub, GitLab, Visual Studio Code oder Notion integrieren, unter anderem. Ebenso gibt es Integrationen für praktisch alle Frontend-Bibliotheken und -Frameworks.

Mit der einfachen Sprache von Mermaid lassen sich Diagramme deklarativ definieren, ohne ein visuelles Zeichenprogramm erlernen zu müssen, was von Natur aus langsamer ist. Außerdem ist es viel einfacher, Code zu pflegen als Gimp- oder El Pato-Quelldateien.

Ich entdeckte es (etwas spät) bei der Erstellung des Lernleitfadens für NLP.

Im Folgenden stelle ich einige Integrationsbeispiele und mit Mermaid erstellte Diagramme vor, gefolgt von einer Anleitung zur Installation und Anpassung in Astro.


Inhaltsverzeichnis

Inhaltsverzeichnis

Beliebte Mermaid-Integrationen

Je nach Stack kann man die offizielle mermaid-Bibliothek oder Community-Wrapper verwenden:


Beispiele

Graph TD

Quellcode:

```mermaid
graph TD
A[Mermaid-Block in MDX schreiben] --> B[Astro verarbeitet ihn]
B --> C[Inline-SVG wird generiert]
C --> D[Diagramm wird im Beitrag gerendert]
```

Gerendertes Diagramm:

graph TD
    A[Mermaid-Block in MDX schreiben] --> B[Astro verarbeitet ihn]
    B --> C[Inline-SVG wird generiert]
    C --> D[Diagramm wird im Beitrag gerendert]

Flowchart LR

Quellcode:

```mermaid
flowchart LR
Start([Start]) --> Schritt1[Ziel definieren]
Schritt1 --> Schritt2[Mermaid-Block schreiben]
Schritt2 --> Schritt3[In Astro rendern]
Schritt3 --> Ende([Ende])
```

Gerendertes Diagramm:

flowchart LR
    Start([Start]) --> Schritt1[Ziel definieren]
    Schritt1 --> Schritt2[Mermaid-Block schreiben]
    Schritt2 --> Schritt3[In Astro rendern]
    Schritt3 --> Ende([Ende])

Sequence Diagram

Quellcode:

```mermaid
sequenceDiagram
participant U as Benutzer
participant M as MDX
participant A as Astro
participant R as Renderer
U->>M: Mermaid-Block schreiben
M->>A: Inhalt kompilieren
A->>R: Inline-SVG generieren
R-->>U: Diagramm anzeigen
```

Gerendertes Diagramm:

sequenceDiagram
    participant U as Benutzer
    participant M as MDX
    participant A as Astro
    participant R as Renderer
    U->>M: Mermaid-Block schreiben
    M->>A: Inhalt kompilieren
    A->>R: Inline-SVG generieren
    R-->>U: Diagramm anzeigen

Mindmap

Quellcode:

```mermaid
mindmap
root((Grundlagen))
Sprache (langue)
Rede (parole)
Sprachkompetenz
Sprachliche Performanz
Sprachzeichen
Synchronie und Diachronie
```

Gerendertes Diagramm:

mindmap
  root((Grundlagen))
    Sprache (langue)
    Rede (parole)
    Sprachkompetenz
    Sprachliche Performanz
    Sprachzeichen
    Synchronie und Diachronie

Class Diagram

Quellcode:

```mermaid
classDiagram
class Tier {
+String name
+fressen()
}
class Hund {
+bellen()
}
Tier <|-- Hund
```

Gerendertes Diagramm:

classDiagram
    class Tier {
      +String name
      +fressen()
    }
    class Hund {
      +bellen()
    }
    Tier <|-- Hund

State Diagram

Quellcode:

```mermaid
stateDiagram-v2
[*] --> Inaktiv
Inaktiv --> Aktiv: starten()
Aktiv --> Pausiert: pausieren()
Pausiert --> Aktiv: fortsetzen()
Aktiv --> [*]: stoppen()
```

Gerendertes Diagramm:

stateDiagram-v2
    [*] --> Inaktiv
    Inaktiv --> Aktiv: starten()
    Aktiv --> Pausiert: pausieren()
    Pausiert --> Aktiv: fortsetzen()
    Aktiv --> [*]: stoppen()

Entity Relationship (ER)

Quellcode:

```mermaid
erDiagram
BENUTZER ||--o{ BESTELLUNG : gibt_auf
BESTELLUNG ||--|{ BESTELLPOSITION : enthaelt
PRODUKT ||--o{ BESTELLPOSITION : erscheint_in
```

Gerendertes Diagramm:

erDiagram
    BENUTZER ||--o{ BESTELLUNG : gibt_auf
    BESTELLUNG ||--|{ BESTELLPOSITION : enthaelt
    PRODUKT ||--o{ BESTELLPOSITION : erscheint_in

Gantt

Quellcode:

```mermaid
gantt
title Veröffentlichungsplan
dateFormat YYYY-MM-DD
section Inhalt
Verfassen :a1, 2026-04-22, 2d
Überarbeitung :a2, after a1, 1d
section Veröffentlichung
Layout :a3, after a2, 1d
Veröffentlichen :milestone, m1, after a3, 0d
```

Gerendertes Diagramm:

gantt
    title Veröffentlichungsplan
    dateFormat  YYYY-MM-DD
    section Inhalt
    Verfassen           :a1, 2026-04-22, 2d
    Überarbeitung       :a2, after a1, 1d
    section Veröffentlichung
    Layout              :a3, after a2, 1d
    Veröffentlichen     :milestone, m1, after a3, 0d

Pie Chart

Quellcode:

```mermaid
pie title Aufgabenverteilung
"Dokumentation" : 35
"Entwicklung" : 45
"Tests" : 20
```

Gerendertes Diagramm:

pie title Aufgabenverteilung
    "Dokumentation" : 35
    "Entwicklung" : 45
    "Tests" : 20

Git Graph

Quellcode:

```mermaid
gitGraph
commit id: "init"
branch feature
checkout feature
commit id: "neues-diagramm"
checkout main
merge feature
commit id: "release"
```

Gerendertes Diagramm:

gitGraph
    commit id: "init"
    branch feature
    checkout feature
    commit id: "neues-diagramm"
    checkout main
    merge feature
    commit id: "release"
Weitere Beispiele hier.

Installationsanleitung für Astro

1. Mermaid installieren

Terminal window
bun add mermaid

2. Remark-Plugin in astro.config.ts

Das Problem mit Build-Zeit-Rendering-Lösungen (wie astro-mermaid oder rehype-mermaid) ist, dass sie Konflikte mit astro-expressive-code erzeugen, der Bibliothek für Syntax-Highlighting, und die Hervorhebung in allen ```javascript, ```bash etc.-Blöcken kaputt machen.

Die Lösung ist, Mermaid clientseitig zu rendern, aber expressiveCode darf die ```mermaid-Blöcke nicht verarbeiten. Dafür wandelt ein Remark-Plugin diese Blöcke in rohen HTML-Code <pre class="mermaid"> um, bevor expressiveCode sie sieht:

astro.config.ts
import { visit } from 'unist-util-visit';
function remarkMermaidBypass() {
return (tree: any) => {
visit(tree, 'code', (node: any, index: number | undefined, parent: any) => {
if (node.lang === 'mermaid' && parent && typeof index === 'number') {
parent.children[index] = {
type: 'html',
value: `<pre class="mermaid">\n${node.value}\n</pre>`,
};
}
});
};
}

In das Array markdown.remarkPlugins einfügen (Reihenfolge wichtig: muss zuerst stehen):

astro.config.ts
markdown: {
remarkPlugins: [
remarkMermaidBypass, // zuerst
remarkToc,
remarkMath,
remarkCollapse,
],
// ...
},

Wichtig ist auch, dass die MDX-Integration die Markdown-Konfiguration erbt, anstatt eigene Plugins zu definieren:

astro.config.ts
mdx({
extendMarkdownConfig: true, // erbt remarkPlugins und rehypePlugins
}),

Werden rehypePlugins oder remarkPlugins direkt an mdx() übergeben, ersetzen sie (und werden nicht mit) denen aus markdown.* zusammengeführt, was dazu führt, dass rehypeExpressiveCode lautlos aus der MDX-Pipeline verschwindet.

3. Rendering-Skript im Post-Layout

Im Layout, das die Beiträge rendert (in Astro Paper: PostDetails.astro), wird ein <script> hinzugefügt, der mermaid importiert und clientseitig rendert. Das Skript verwendet das Ereignis astro:page-load für Kompatibilität mit View Transitions und einen MutationObserver, der bei Theme-Wechsel (hell/dunkel) neu rendert:

PostDetails.astro
import mermaid from "mermaid";
let themeObserver: MutationObserver | null = null;
function getTheme() {
return document.documentElement.dataset.theme === "dark" ? "dark" : "forest";
}
async function renderMermaid() {
// Bereits gerenderte Diagramme zu pre.mermaid zurücksetzen für erneutes Rendern
// (beim Theme-Wechsel erforderlich)
document.querySelectorAll<HTMLElement>("[data-mermaid]").forEach(el => {
const pre = document.createElement("pre");
pre.className = "mermaid";
pre.textContent = el.dataset.mermaid!;
el.replaceWith(pre);
});
const blocks = Array.from(document.querySelectorAll<HTMLPreElement>("pre.mermaid"));
if (!blocks.length) return;
mermaid.initialize({ startOnLoad: false, theme: getTheme() });
await Promise.all(blocks.map(async pre => {
const source = (pre.textContent ?? "").trim();
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
try {
const { svg } = await mermaid.render(id, source);
const wrapper = document.createElement("div");
wrapper.className = "my-6 flex justify-center overflow-x-auto";
wrapper.dataset.mermaid = source; // Source für erneutes Rendern beim Theme-Wechsel speichern
wrapper.innerHTML = svg;
pre.replaceWith(wrapper);
} catch (err) {
console.error("[mermaid]", err);
}
}));
}
document.addEventListener("astro:page-load", () => {
renderMermaid();
themeObserver?.disconnect();
themeObserver = new MutationObserver(renderMermaid);
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
});
// falsch
const codeBlocks = Array.from(document.querySelectorAll("pre"));
// richtig
const codeBlocks = Array.from(document.querySelectorAll("pre:not(.mermaid)"));

4. Theme-Erkennung

Die Website verwendet das Attribut data-theme am <html>-Element, um das aktive Theme anzuzeigen ("light" oder "dark"). Die Funktion getTheme() liest dieses Attribut und gibt den entsprechenden Mermaid-Theme-Namen zurück. Dieser Blog verwendet "forest" für hell und "dark" für dunkel.

Farbanpassung mit CSS

Mermaid rendert SVG inline, und seine internen Styles verwenden hochspezifische Selektoren (#id .klasse). Um sie mit den Theme-Farben der Website zu überschreiben, ist !important im globalen CSS erforderlich.

In Mermaid v11 gibt es einige Besonderheiten gegenüber früheren Versionen:

Das vollständige in diesem Blog verwendete CSS in global.css:

/* Mermaid: mindmap — nicht-Root-Abschnitte verwenden die Vordergrundfarbe des Themes */
svg.mindmapDiagram [class*="section-"]:not(.section-root) span {
color: var(--foreground) !important;
}
/* Mermaid: flowchart — Knoten (graph TD, flowchart LR, etc.) */
[id^="mermaid-"] .node rect,
[id^="mermaid-"] .node circle,
[id^="mermaid-"] .node ellipse,
[id^="mermaid-"] .node polygon,
[id^="mermaid-"] .node path {
fill: var(--muted) !important;
stroke: var(--border) !important;
}
/* Mermaid: flowchart — Kanten */
[id^="mermaid-"] .edgePath .path,
[id^="mermaid-"] .flowchart-link {
stroke: var(--accent) !important;
}
/* Mermaid: flowchart — Pfeilspitzen (v11: .arrowheadPath) */
[id^="mermaid-"] .arrowheadPath {
fill: var(--accent) !important;
stroke: var(--accent) !important;
}
/* Mermaid: sequence — Akteure (v11: .actor direkt auf rect) */
[id^="mermaid-"] .actor {
fill: var(--muted) !important;
stroke: var(--border) !important;
}
/* Mermaid: sequence — Nachrichtenlinien */
[id^="mermaid-"] .messageLine0,
[id^="mermaid-"] .messageLine1 {
stroke: var(--accent) !important;
}
/* Mermaid: sequence — Pfeilspitzen */
[id$="-arrowhead"] path {
fill: var(--accent) !important;
stroke: var(--accent) !important;
}
/* Mermaid: sequence — vertikale Lifelines */
[id^="mermaid-"] .actor-line {
stroke: var(--border) !important;
}

Share this post on:

Previous Post
Leitfaden zur Verwaltung von Diensten mit systemd.
Next Post
Wie man caveman in Visual Studio Code für Claude Code installiert.