• #draw.io
  • #vue
  • #nuxt
  • #diagram
  • #調査レポート
未分類

Draw.ioファイルの動的埋め込み調査レポート

調査目的

Draw.ioファイル(.drawio)をVueコンポーネント内で動的に読み込み、ページをリロードするだけで最新の図が反映される仕組みを実現する。

調査結果サマリー

結論:成功 ✓

viewer-static.min.jsを使用することで、Draw.ioファイルを動的に読み込んで表示できることを確認した。

成功した方法

  • 使用ライブラリ: https://viewer.diagrams.net/js/viewer-static.min.js
  • 実装方法: data-mxgraph属性を持つdiv要素にXMLデータをJSONで渡す
  • CORS問題: 解決済み(ローカルファイルを直接読み込むため、CORSエラーなし)

調査プロセス

試行1: iframe + URL指定(失敗)

方法:

viewerUrl.value = `https://viewer.diagrams.net/?url=${encodeURIComponent(fileUrl)}`

結果: ❌ 失敗

  • Draw.ioビューアーはlocalhostのURLを読み込めない(CORS制限)
  • エラー: "ファイルが見つかりません"

試行2: iframe + Base64エンコード(失敗)

方法:

const base64 = btoa(unescape(encodeURIComponent(text)))
viewerUrl.value = `https://viewer.diagrams.net/#R${base64}`

結果: ❌ 失敗

  • エラー: "invalid code lengths set"
  • Draw.ioビューアーが期待する圧縮形式と不一致

試行3: viewer-static.min.js(成功)

方法:

<template>
  <div
    class="mxgraph"
    :data-mxgraph="diagramData"
  ></div>
</template>

<script setup>
const diagramData = ref('')

async function loadDiagram() {
  const response = await fetch('/2025-11-18/freee-ai-system.drawio')
  const xml = await response.text()

  diagramData.value = JSON.stringify({
    highlight: '#0000ff',
    nav: true,
    resize: true,
    toolbar: 'zoom layers tags lightbox',
    edit: '_blank',
    xml: xml
  })

  loadViewerScript()
}

function loadViewerScript() {
  const script = document.createElement('script')
  script.src = 'https://viewer.diagrams.net/js/viewer-static.min.js'
  script.async = true
  document.body.appendChild(script)
}
</script>

結果: ✅ 成功

  • Draw.ioファイルが正常に表示される
  • インタラクティブな操作(ズーム、レイヤー表示など)が可能
  • CORSエラーなし

実装の詳細

前提条件

Nuxtサーバーミドルウェアで.drawioファイルを提供できるように設定:

// server/middleware/content-images.ts
const match = url.match(/^\/(\d{4}-\d{2}-\d{2})\/([^/]+\.(png|jpg|jpeg|gif|webp|svg|drawio))$/i)

const mimeTypes: Record<string, string> = {
  // ...
  drawio: 'application/xml'
}

データフロー

data-mxgraph属性の構造

{
  "highlight": "#0000ff",    // ハイライト色
  "nav": true,                // ナビゲーション表示
  "resize": true,             // リサイズ可能
  "toolbar": "zoom layers tags lightbox",  // ツールバー機能
  "edit": "_blank",          // 編集ボタンで新しいタブで開く
  "xml": "<mxfile>...</mxfile>"  // Draw.io XML データ
}

メリット・デメリット

メリット

CORSエラーなし: ローカルファイルを直接読み込むため、localhostでも動作 ✅ iframeなし: ページに直接埋め込まれるため、スタイリングが容易 ✅ 公式ライブラリ: Draw.io公式のviewer-static.min.jsを使用 ✅ インタラクティブ: ズーム、レイヤー表示などの操作が可能 ✅ 動的更新: ファイルを編集してページをリロードするだけで反映 ✅ Gitで管理: .drawioファイルはテキストベースのXMLなので、バージョン管理が容易

デメリット

⚠️ 外部依存: viewer.diagrams.netのCDNに依存 ⚠️ 初回ロード時間: JavaScriptライブラリのロードに若干時間がかかる ⚠️ ビルド時非レンダリング: SSR/SSGでは描画されない(クライアントサイドのみ)

代替案の検討

代替案1: SVG自動変換

概要: Draw.ioファイルの変更を監視して、自動的にSVGに変換するスクリプトを作成

メリット:

  • SSR/SSGで描画可能
  • CDN依存なし

デメリット:

  • ビルドプロセスが複雑化
  • インタラクティブ性なし
  • 変換スクリプトの保守が必要

代替案2: Nuxtサーバーサイドプロキシ

概要: Nuxtサーバー経由でDraw.ioビューアーにファイルを提供

メリット:

  • CORS問題を回避可能
  • iframe方式でも動作

デメリット:

  • サーバーサイドの実装が必要
  • プロキシ経由の通信がオーバーヘッド

推奨実装

現時点ではviewer-static.min.js方式を推奨する。

理由:

  1. 実装がシンプル
  2. CORSエラーなし
  3. インタラクティブ性あり
  4. 公式ライブラリで安定

実装例

実際の動作例は以下で確認できます:

  • デモページ: /blog/drawio-viewer-test
  • ソースコード: apps/web/app/pages/blog/drawio-viewer-test.vue
  • Draw.ioファイル: apps/web/content/2025-11-18/freee-ai-system.drawio

課題と解決

キャッシュ問題による動的更新の遅延

問題: 初期実装では、.drawioファイルを編集してページをリロードしても、変更が即座に反映されなかった。サーバーを再起動する必要があった。

原因:

  1. サーバーミドルウェア(server/middleware/content-images.ts)が全てのファイルにcache-control: public, max-age=31536000(1年間のキャッシュ)を設定
  2. ブラウザのfetchがキャッシュを使用

解決策:

1. サーバーミドルウェアでの開発環境用キャッシュ制御

// server/middleware/content-images.ts
const isDev = process.env.NODE_ENV === 'development'
const cacheControl = (ext === 'drawio' && isDev)
  ? 'no-cache, no-store, must-revalidate'  // 開発環境では即座に反映
  : 'public, max-age=31536000'              // 本番環境では長期キャッシュ

2. Vueコンポーネントでのキャッシュバスティング

// pages/blog/drawio-viewer-test.vue
const response = await fetch(`/2025-11-18/freee-ai-system.drawio?t=${Date.now()}`, {
  cache: 'no-store'
})

3. サーバーミドルウェアでクエリパラメータ対応

// server/middleware/content-images.ts
const pathname = url.split('?')[0]  // クエリパラメータを除去
const match = pathname.match(/^\/(\d{4}-\d{2}-\d{2})\/([^/]+\.(png|jpg|jpeg|gif|webp|svg|drawio))$/i)

結果: ✅ 解決済み

  • .drawioファイルを編集
  • ページをリロード(サーバー再起動不要
  • 変更が即座に反映される

開発環境で真の「動的埋め込み」が実現した。

本番環境での「Not a diagram file」エラー

問題: Cloudflare Pagesにデプロイすると「Not a diagram file」エラーが発生

原因: nuxt.config.tsのNitroフックで、画像ファイル(png, jpg等)のみをpublic/ディレクトリにコピーしており、.drawioファイルが本番ビルドに含まれていなかった

解決策:

// nuxt.config.ts
} else if (/\.(png|jpg|jpeg|gif|webp|svg|drawio)$/i.test(entry.name)) {
  // .drawioを正規表現に追加

結果: ✅ 解決済み

  • ビルド時に.drawioファイルもpublicディレクトリにコピー
  • 本番環境でも正常に表示

今後の課題

1. オフライン対応

CDNに依存しているため、オフライン環境では動作しない。

解決策:

  • viewer-static.min.jsをローカルにホスト
  • public/js/viewer-static.min.jsに配置して参照

2. SSR/SSG対応

現在はクライアントサイドのみで描画される。

解決策:

  • サーバーサイドでSVGに変換してレンダリング
  • または、静的ビルド時にSVGを生成

3. パフォーマンス最適化

大きな図ではロード時間がかかる可能性がある。

解決策:

  • 遅延ロード(Intersection Observer)
  • 複数の図がある場合は、1つのviewer-static.min.jsを共有

参考資料

まとめ

Draw.ioファイルをVueコンポーネント内で動的に読み込む方法として、viewer-static.min.jsを使用する実装が最も実用的であることが確認できた。

この方法により:

  • ✅ Draw.ioファイルを編集したら、ページをリロードするだけで反映
  • ✅ CORSエラーなしでlocalhostでも動作
  • ✅ SVG/PNGへのエクスポート不要
  • ✅ Gitで履歴管理可能

動的埋め込みは可能であり、実用的な実装方法が確立された。