Astro ブログで Mermaid 図をレンダリングする — rehype-mermaid + Playwright で静的 SVG 生成

Astro ベースのブログで Markdown/MDX の Mermaid コードブロックをビルド時に SVG に変換する方法。rehype-mermaid と Playwright を使った静的生成のセットアップと、各選択肢の比較。

はじめに

Astro で構築したブログに Mermaid 図を埋め込みたい。ただし:

これを満たすのが rehype-mermaid + Playwright でビルド時に SVG 変換する構成です。この記事では、選択肢の比較と具体的なセットアップを整理します。

選択肢の比較

graph TB
    Input["Markdown 内の<br/>```text ブロック"]

    Input --> Choice{どの戦略?}

    Choice -->|"inline-svg"| StaticSVG["rehype-mermaid<br/>+ Playwright<br/>ビルド時に SVG 生成"]
    Choice -->|"client"| ClientJS["mermaid.js<br/>ブラウザで実行"]
    Choice -->|"img-svg"| ImgTag["SVG を別ファイルに<br/>&lt;img&gt; で参照"]

    StaticSVG --> Result1["軽い・JS不要<br/>ビルドに時間"]
    ClientJS --> Result2["ビルド速い<br/>JS 必須・遅延"]
    ImgTag --> Result3["キャッシュ効く<br/>インライン編集不可"]

    style StaticSVG fill:#16a34a,color:#fff
    style ClientJS fill:#f59e0b,color:#fff
    style ImgTag fill:#6366f1,color:#fff
戦略メリットデメリット
inline-svg (推奨)ページが軽い、JS 不要、SEO に強いビルド時間が伸びる、Playwright 依存
clientビルドが速い、ツール不要JS 必須、描画が一瞬遅れる、SEO が弱い
img-svgキャッシュが効くインライン編集できない、手間

アーキテクチャ

今回の構成はこうなります。

graph LR
    MDX["記事 (.mdx)<br/>```text"]
    Astro["Astro ビルド"]
    Rehype["rehype-mermaid"]
    Mermaid["mermaid-isomorphic"]
    Playwright["Playwright<br/>(Chromium)"]
    SVG["インライン SVG"]
    HTML["静的 HTML"]

    MDX --> Astro
    Astro --> Rehype
    Rehype --> Mermaid
    Mermaid --> Playwright
    Playwright --> SVG
    SVG --> HTML

    style Playwright fill:#45ba4b,color:#fff
    style SVG fill:#ff3670,color:#fff

ビルド時に Playwright でヘッドレスブラウザを起動し、Mermaid を実行して SVG を生成、HTML に埋め込みます。最終成果物は純粋な HTML + SVG なので、ユーザーのブラウザで JS は一切動きません。

セットアップ手順

1. パッケージのインストール

npm install rehype-mermaid playwright
npx playwright install chromium

rehype-mermaid の内部で mermaid-isomorphic が Playwright を使うので、両方必要です。

2. astro.config.mjs を更新

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import mdx from '@astrojs/mdx';
import rehypeMermaid from 'rehype-mermaid';

export default defineConfig({
  integrations: [react(), mdx()],
  markdown: {
    syntaxHighlight: {
      type: 'shiki',
      excludeLangs: ['mermaid'],  // mermaid ブロックは Shiki でハイライトしない
    },
    rehypePlugins: [
      [rehypeMermaid, { strategy: 'inline-svg' }],
    ],
  },
});

ポイントは excludeLangs: ['mermaid']。これがないと Shiki が先に mermaid ブロックをシンタックスハイライトしてしまい、rehype-mermaid が処理できなくなります。

3. GitHub Actions で Playwright をインストール

CI でビルドする場合、Playwright の Chromium が必要です。

# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci

      # ← これを追加
      - name: Install Playwright Chromium
        run: npx playwright install --with-deps chromium

      - run: npm run build

--with-deps で Chromium 実行に必要な Linux ライブラリも自動インストールされます。

動作確認

ビルドして SVG が埋め込まれているか確認:

npm run build
grep -c '<svg' dist/blog/your-post/index.html

数字が出れば SVG が埋め込まれています。

記法

記事の中では普通の fenced code block として書くだけ。

```text
graph TB
    A[開始] --> B{条件}
    B -->|Yes| C[処理A]
    B -->|No| D[処理B]
    C --> E[終了]
    D --> E
```

レンダリング結果:

graph TB
    A[開始] --> B{条件}
    B -->|Yes| C[処理A]
    B -->|No| D[処理B]
    C --> E[終了]
    D --> E

対応している図の種類

Mermaid が対応している図なら何でも書けます。

シーケンス図

sequenceDiagram
    participant B as ブラウザ
    participant A as Astro ビルド
    participant P as Playwright

    B->>A: 記事リクエスト
    A->>A: MDX パース
    A->>P: mermaid 実行
    P->>A: SVG 返却
    A->>B: HTML + inline SVG

ER 図

erDiagram
    USER ||--o{ POST : writes
    POST ||--o{ COMMENT : has
    USER {
        string id
        string email
    }
    POST {
        string id
        string title
        string content
    }

状態遷移図

stateDiagram-v2
    [*] --> Draft
    Draft --> Review: 提出
    Review --> Published: 承認
    Review --> Draft: 差し戻し
    Published --> Archived: 一定期間後
    Archived --> [*]

注意点

ビルド時間が伸びる

記事数 × 図の数だけ Chromium が起動するので、大量の図があるとビルドが遅くなります。キャッシュ戦略も検討の余地あり。

テーマのカスタマイズ

デフォルトのテーマが合わない場合、Mermaid の設定で変更できます。

rehypeMermaid({
  strategy: 'inline-svg',
  mermaidConfig: {
    theme: 'dark',
    themeVariables: {
      primaryColor: '#6366f1',
      primaryTextColor: '#fff',
    },
  },
})

Shiki との衝突

syntaxHighlight.excludeLangs で mermaid を除外しないと、Shiki が先にハイライト処理してしまって rehype-mermaid が動きません。これにハマりました。

markdown: {
  syntaxHighlight: {
    type: 'shiki',
    excludeLangs: ['mermaid'],  // 必須
  },
}

ローカル開発時

npm run dev でも Playwright が起動するので、初回は少し待たされます。以降はキャッシュされます。

まとめ

← Back to all posts