Next.jsのMDXにMermaidダイアグラムを追加する

読了時間: 4分
Next.jsのMDXにMermaidダイアグラムを追加する のサムネイル

はじめに

技術ブログやドキュメントサイトでは、フローチャートやシーケンス図などのダイアグラムを使ってシステム設計や処理の流れを視覚的に説明したい場面が多くあります。Mermaidは、テキストベースの記法でダイアグラムを描画できるツールです。この記事では、Next.jsのMDXを使用したブログにMermaidダイアグラムを追加する実装方法を、コード例とともに解説します。

Mermaid.jsとは

Mermaid(Mermaid.js)は、テキストベースの記法でフローチャート、シーケンス図、クラス図、ガントチャートなどを描画できるJavaScriptライブラリです。GitHubやNotionなど多くのプラットフォームでサポートされており、技術ドキュメントに広く利用されています。以降、本文では「Mermaid」と表記します。

たとえば、Mermaidを使うと以下のようなテキスト記法から図表を生成できます。

Markdown
```mermaid
graph LR
  A[開始] --> B[処理]
  B --> C[終了]
```
Flowchart

この記事で実現すること

  • MDXファイル内でのmermaidコードブロック使用
  • 既存のシンタックスハイライターとのコンフリクト回避
  • ライトモード・ダークモードへの対応
  • フローチャート、シーケンス図、クラス図、ガントチャートなど多様なダイアグラム

前提条件

  • Next.js 15以降
  • next-mdx-remoteによるMDX処理
  • 既存のシンタックスハイライター使用時は、コンフリクト回避の実装が必要

Mermaidパッケージのインストール

必要なパッケージをインストールします。

Bash
npm install mermaid

TypeScript使用時は型定義も含まれます。

Mermaidコンポーネントの実装

Mermaidダイアグラムを表示するReactコンポーネントを作成します。Mermaidの描画処理はブラウザのDOM操作を必要とするため、クライアントサイドでレンダリングします。そのため、"use client"ディレクティブが必要になります。

src/components/Mermaid.tsxTSX
"use client";

import { useEffect, useRef, useState } from "react";

interface MermaidProps {
  chart: string;
}

export function Mermaid({ chart }: MermaidProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let mounted = true;

    const renderDiagram = async () => {
      if (!containerRef.current) return;

      try {
        setError(null);

        // 動的にmermaidをインポート
        const mermaid = (await import("mermaid")).default;

        // 初期化
        mermaid.initialize({
          startOnLoad: false,
          theme: "default",
        });

        // ユニークなIDを生成
        const id = `mermaid-${Math.random().toString(36).slice(2, 11)}`;

        // SVGをレンダリング
        const { svg } = await mermaid.render(id, chart);

        if (mounted && containerRef.current) {
          containerRef.current.innerHTML = svg;
        }
      } catch (err) {
        if (mounted) {
          setError(
            err instanceof Error
              ? err.message
              : "ダイアグラムの描画に失敗しました"
          );
          console.error(err);
        }
      }
    };

    renderDiagram();

    return () => {
      mounted = false;
    };
  }, [chart]);

  if (error) {
    return (
      <div className="p-4 bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200 rounded-lg">
        {error}
      </div>
    );
  }

  return (
    <div
      ref={containerRef}
      className="my-6 overflow-x-auto"
    />
  );
}

実装のポイント

このMermaidコンポーネントでは、次の点に注意しています。

  1. 動的インポート: await import("mermaid")によりコード分割し、必要なときだけMermaidをロードします
  2. クリーンアップ: unmount時のフラグ管理でメモリリークを防止します
  3. エラーハンドリング: 型安全なエラーチェック(instanceof Error)で適切にエラーメッセージを表示します
  4. ユニークID生成: Math.random()による動的ID生成はuseEffect内で実行されるため、SSRとクライアントで差分が発生しません
  5. ダークモード対応: Tailwindクラスでライト・ダークモード両対応のスタイリングを実現します
  6. セキュリティ: Mermaidライブラリが生成する信頼できるSVGを使用。ユーザー入力を直接renderに渡す場合は検証が必要です

テーマ対応(オプション)

ダークモード対応が必要な場合は、next-themesなどのライブラリと組み合わせます。

TSX
import { useTheme } from "next-themes";

export function Mermaid({ chart }: MermaidProps) {
  const { resolvedTheme } = useTheme();
  
  useEffect(() => {
    let mounted = true;

    const renderDiagram = async () => {
      if (!containerRef.current) return;

      try {
        setError(null);
        const mermaid = (await import("mermaid")).default;

        mermaid.initialize({
          startOnLoad: false,
          theme: resolvedTheme === "dark" ? "dark" : "default",
        });

        const id = `mermaid-${Math.random().toString(36).slice(2, 11)}`;
        const { svg } = await mermaid.render(id, chart);

        if (mounted && containerRef.current) {
          containerRef.current.innerHTML = svg;
        }
      } catch (err) {
        if (mounted) {
          setError(
            err instanceof Error
              ? err.message
              : "ダイアグラムの描画に失敗しました"
          );
        }
      }
    };

    renderDiagram();

    return () => {
      mounted = false;
    };
  }, [chart, resolvedTheme]);
  
  // ...
}

Mermaidはdefaultdarkの他にも、forestneutralなど複数のテーマを提供しています。詳細は公式ドキュメントを参照してください。

MDXコンポーネントへの統合

next-mdx-remoteを使用している場合は、mdx-components.tsxファイルでカスタムコンポーネントを定義します。

mdx-components.tsxTSX
import React from "react";
import type { MDXComponents } from "mdx/types";
import { Mermaid } from "./components/Mermaid";

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
    // preタグをカスタマイズ
    pre: ({ children, ...props }) => {
      // 型安全なチェック
      if (
        React.isValidElement(children) &&
        children.props &&
        typeof children.props.className === "string"
      ) {
        const className = children.props.className;
        const language = className.replace("language-", "");
        const codeString = children.props.children;

        // mermaidの場合は専用コンポーネントを使用
        if (language === "mermaid" && typeof codeString === "string") {
          return <Mermaid chart={codeString} />;
        }
      }

      // それ以外は通常のpreタグで表示
      return <pre {...props}>{children}</pre>;
    },
  };
}

コンフリクトを回避する仕組み

MDXはpreタグとcodeタグの組み合わせでコードブロックを表現します。言語情報はcodeタグのclassNamelanguage-xxx形式で格納されるため、これを利用した分岐処理が可能です。

Flowchart

この言語判定の仕組みなら、既存のシンタックスハイライターに影響を与えずMermaidを統合できます。

既存のコードブロックとの共存

Mermaidを導入しても、既存のシンタックスハイライターは正常に動作します。カスタムpreコンポーネント内で言語判定を行っているため、mermaid以外のコードブロックは通常どおり処理されます。例えば、以下のTypeScriptコードは通常のシンタックスハイライトで処理されます。

TypeScript
// TypeScriptコード - 通常のシンタックスハイライトで処理
interface User {
  id: string;
  name: string;
  email: string;
}

function greetUser(user: User): string {
  return `Hello, ${user.name}!`;
}

MDXファイルでの使用方法

MDXファイル内で、mermaidコードブロックを使用してダイアグラムを記述できます。

Markdown
```mermaid
graph TD
  A[開始] --> B[処理]
  B --> C[終了]
```

Mermaidの記法は多様で覚えることが多いですが、GitHub CopilotなどのAIアシスタントを活用すれば、記法を細かく理解していなくてもダイアグラムを作成できます。

「フローチャートを作成して」「この処理をシーケンス図にして」といった自然言語のプロンプトから、適切なMermaid記法を生成してくれます。

主な図の種類

Mermaidが対応している主なダイアグラムタイプは次の通りです。

ダイアグラム名用途
Flowchart処理フロー・条件分岐の可視化
Sequence Diagramシステム間の通信・メッセージングの表現
Class Diagramクラス構造・継承関係の設計図
State Diagram状態遷移・ライフサイクルの表現
Entity Relationship Diagramデータベーススキーマ・テーブル関係
Ganttプロジェクトスケジュール・タスク管理
Pie Chart割合・比率の可視化
Git GraphGitブランチ戦略・履歴の表現
User Journeyユーザー体験・顧客行動の可視化
Mindmap思考整理・概念マップ

Mermaidの記法の詳細は公式ドキュメントを参照してください。

フローチャート

条件分岐やループを含む処理フローを視覚化できます。

Flowchart

シーケンス図

システム間の通信やAPIコールの流れを表現できます。

Sequence Diagram

クラス図

オブジェクト指向設計におけるクラス構造を図示できます。

Class Diagram

ガントチャート

プロジェクトのスケジュールやタスクの進行状況を可視化できます。

Gantt Chart

まとめ

Next.jsのMDXブログにMermaidダイアグラムを追加することで、技術記事やドキュメントの表現力が大きく向上します。

本記事で実装した主なポイント:

  • 動的インポートによるコード分割とパフォーマンス最適化
  • エラーハンドリングによる堅牢性の確保
  • ダークモード対応によるユーザー体験の向上
  • 既存のシンタックスハイライターとの共存

Mermaidは本記事で紹介した以外にも、状態遷移図(State Diagram)、ER図(Entity Relationship Diagram)、マインドマップなど、多様なダイアグラムタイプをサポートしています。詳細はMermaid公式ドキュメントを参照してください。

この記事はお役に立ちましたか?

皆様の フィードバック が励みになります!

ボタンを押して記事が役に立ったことを伝える

筆者にコーヒーを奢る(Buy Me a Coffee)

コーヒーを奢る

この記事をシェア

X
Facebook
はてな
URLをコピー
utsusieのプロフィール画像

utsusie

UI Designer / Web Director

Lv.0
STR
LDR
EXP
TEC
LCK