RailsでMermaid記法を動かすためのアーキテクチャ解説

RailsでMermaid記法を動かすためのアーキテクチャ解説の説明画像

「描く」から「生成する」へ:AIとMermaidの強力な親和性

これまで、フロー図やシーケンス図を作成するには、Draw.ioや各種ドローツールを開き、
マウスを使って時間をかけてボックスや矢印を配置するのが一般的だったように感じます。
しかし、生成AIが日常的なツールとなった今、この「手作業で描く」スタイルは大きな転換点を迎えています。

LLM(大規模言語モデル)は構造化されたテキストの出力が非常に得意です。
そのため、GUIツールで図を作成するよりも、「AIにMermaid記法のコードを吐かせ、それをそのまま貼り付ける」方が圧倒的に速く
、かつ正確に図解を行えます。

「AIが図の構造を考え、エンジニアはそれをMarkdownに貼り付けるだけ」。
このワークフローと最も相性が良いのが、テキストベースで図を定義するMermaidです。

本記事では、RailsアプリケーションのブログにMermaid.jsを組み込み、
AIとの親和性を最大限に活かしながら、Markdownを書くだけでメンテナンス性の高い図を自動生成するアーキテクチャ
について解説します。

アーキテクチャ全体像

単にライブラリを読み込むだけでなく、「サーバー側でのマークアップ」と「クライアント側での描画」の役割を明確に分離しました。

1. サーバー側:Markdownレンダラーの拡張

Markdownを解析する際、Mermaidのコードブロックを「ただのコード」としてではなく、「図の種」として出力します。

  • Gem選定: redcarpet を採用。高速かつ、レンダリング処理を自由にオーバーライドできるためです。
  • カスタムレンダラー: block_code メソッドを上書きし、言語指定が mermaid の場合のみ <div class="mermaid"> を出力するように変更。これにより、既存のシンタックスハイライト(Prism.js等)との干渉を防ぎます。
  • セキュリティ: html_escape を通すことで、図の定義内に悪意のあるスクリプトが混入するリスク(XSS)を排除しています。

2. クライアント側:ライフサイクルに合わせた描画制御

Rails特有の「画面遷移(Turbo/Turbolinks)」と「描画パフォーマンス」の両立がキモです。

  • 遅延ロード: 常にMermaidを読み込むのではなく、ページ内に .mermaid クラスが存在する時だけ CDN から動的にロード。
  • 二重描画の防止: data-rendered 属性を活用。Turboによるページ復元時や、動的なコンテンツ追加時に、「まだ図解されていないノードだけ」を狙って mermaid.run() を実行します。

3. プレゼンテーション層:レスポンシブ対応

MermaidのSVGは、放っておくと画面幅を突き抜けることがあります。
CSSで「枠」と「余白」を制御し、スマホ閲覧時は横スクロールを許容する設計にしました。

実装のコア・スニペット

サーバーサイド(Ruby)

パーサーを拡張し、特定のフェンス(```mermaid)を検知するロジックです。

# app/helpers/custom_markdown_renderer.rb
class CustomMarkdownRenderer < Redcarpet::Render::HTML
  def block_code(code, language)
    return '' if code.nil?
    lang = language.to_s.strip.downcase

    if lang == 'mermaid'
      # Mermaid.jsが解釈できる形式に変換
      %(<div class="mermaid">#{ERB::Util.html_escape(code)}</div>)
    else
      # 通常のコードハイライト用
      %(<pre><code class="language-#{ERB::Util.html_escape(lang)}">#{ERB::Util.html_escape(code)}</code></pre>)
    end
  end
end

クライアントサイド(JavaScript)

Turbo環境下でも安定して動作させるためのハンドリング例です。

// Mermaidの初期化と実行
const initMermaid = async () => {
  const targets = document.querySelectorAll('.mermaid:not([data-rendered])');
  if (targets.length === 0) return;

  // 動的インポート(必要な時だけロード)
  const { default: mermaid } = await import('[https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs](https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs)');

  mermaid.initialize({ startOnLoad: false, theme: 'neutral' });
  await mermaid.run({ nodes: targets });

  // 描画済みフラグを立てる
  targets.forEach(n => n.dataset.rendered = 'true');
};

// Turboの遷移イベントに合わせて発火
document.addEventListener("turbo:load", initMermaid);

お披露目:シーケンス図サンプル

sequenceDiagram participant U as User participant R as Rails participant M as Mermaid U->>R: /modern_public_blog_articles/:id R-->>U: HTML + .mermaid U->>M: JSロード M-->>U: SVG生成

ブラウザ上では、中央寄せのSVGとして表示されます。コードブロックのコピー機能や他のハイライト表示は従来どおり維持できました。

まとめ

ちょうどブログにも図を使った説明を使っていきたいと思っていたところだったので、意外に簡単に導入できてよかったです!
ぜひ皆さんも表現の幅を広げるために導入してみてはいかがでしょうか?

この記事をシェア