[Astro #49] IndexedDBを活用した3Dアセットの完全キャッシュ化とバージョニング管理

[Astro #49] IndexedDBを活用した3Dアセットの完全キャッシュ化とバージョニング管理

はじめに

WebXR環境(React Three Fiber + Astro)において、VRMモデルやGLBステージ、巨大なWebPスカイボックスなどの読み込み時間は、ユーザー体験を損なう最大のボトルネックでした。

今回は、ブラウザのローカルストレージである「IndexedDB」を活用し、重い3Dアセットを完全キャッシュ化。

さらに、更新漏れ(古いキャッシュが残り続ける問題)を防ぐための「JSONベースのバージョニング戦略」を実装し、リロードしても即座に世界が復元される爆速の「Wired」環境を構築しました。

[Astro #49] IndexedDBを活用した3Dアセットの完全キャッシュ化とバージョニング管理

1. キャッシュ&バージョニングの基本戦略

アセット(バイナリデータ)を IndexedDB に保存する際、最も厄介なのが「ファイルの中身を更新したのに、ブラウザが古いキャッシュを読み込み続けてしまう」という問題です。

これを解決するため、以下のような役割分担を行いました。

  • JSONファイル(サーバー側) : アセットのパスと「バージョン情報(version)」を記載した司令塔。これは容量が数KBと軽いため、常に最新のものをネットワークから取得する。
  • IndexedDB(ブラウザ側) : Dexie.js を使用し、重いバイナリデータ(Blob)と、その時点のバージョン情報をセットで保管する。

【判定ロジック】

  1. サーバーから取得したJSONの version と、IndexedDB内の version を比較する。
  2. 一致すれば(CACHE_HIT) :ネットワーク通信を行わず、DBから爆速でBlob URLを生成して読み込む。
  3. 不一致なら(DOWNLOAD) :新しく fetch してバイナリを取得し、DBを新しいデータとバージョンで上書き保存する。

これにより、JSONのバージョン文字列を書き換えるだけで、世界中のユーザーのキャッシュを自動的にパージ・更新できる堅牢なマスターキーが完成しました。

2. 実装のコア:db.tsassetCache.ts

IndexedDBの複雑な操作を隠蔽し、使いやすくするために Dexie.js を導入しました。

データベースの定義 (src/lib/db.ts) モデル、アニメーション、ステージ、スカイボックスごとにテーブル(Store)を分割しました。

注意点:Vite環境でビルドエラーを防ぐため、Table は必ず import type で読み込むこと。

キャッシュ取得ユーティリティ (src/utils/assetCache.ts) 任意のテーブルとJSONエントリーを渡し、URLを解決する共通関数 getCachedAssetUrl を作成。 内部では、先述の「バージョン比較→ヒットならBlob URL返却、ミスならDLしてDB更新」という処理を一手に引き受けています。

3. 各コンポーネントへの適用

準備したユーティリティを、React Three Fiber の各コンポーネントに組み込みました。

アバター(VRM)とステージ(GLB)

WiredAvatarWiredStage では、Reactの useEffect を使って非同期でBlob URLを解決。URLが確定してから useGLTFuseWiredVRM フックに渡すように処理を分離しました。

また、スカイボックスコンポーネントへの Props 渡し間違い(texturePath ではなく data を渡す)による画面暗転バグも修正し、正常なキャッシュ動作を確認しました。

アニメーション(VRMA)のシームレス化

useWiredAnimation.ts 内の loadAndCrossfade 関数を非同期(async)化。 ローダーがアニメーションを読み込む直前に getCachedAssetUrl を経由させることで、状態(Phase)が切り替わって新しいモーションが必要になるたびに発生していた「読み込みのラグ」が完全に消滅しました。キャラクターに真の生命感が宿った瞬間です。

4. 開発中に遭遇したエラーと対処法

実装を進める中で、Reactのステート更新が活発になったことで顕在化したエラーがありました。

  • Manifest 404エラーの連発

  • 原因:アニメーションの切り替え等でステートが更新され、DOMツリーが再評価されるたびに、存在しない manifest.webmanifest をブラウザが探しに行っていた。

  • 解決策:public フォルダに最小限の有効なJSONを記述した manifest.webmanifest を配置し、ブラウザに正常にキャッシュさせることで沈静化。

  • Astro Dev Toolbar の 504 Gateway Timeout

  • 原因:重いアセットのロードや頻繁なリロードにより、Astroの開発用サーバーが処理落ちし、Dev Toolbar用のスクリプト返却がタイムアウトしていた。

  • 解決策:VR開発においてはDOM監視用のDev Toolbarは不要と判断し、astro.config.mjs にて devToolbar: { enabled: false } を設定して完全無効化。コンソールが非常にクリーンになった。

まとめ

「とりあえず動く」状態から脱却し、DB設計という強固な基盤を敷いたことで、今後の拡張性が飛躍的に向上しました。 リロードのたびに数秒〜数十秒待たされていた状態から、瞬時に空間とアバターが立ち上がる「真のWired」体験へ。探索的プロトタイピングのフェーズを経て、ついに本番環境に耐えうる舞台装置が完成しました。