• #Cloudflare R2
  • #音声管理
  • #dev middleware
  • #Nuxt
  • #eurekapu
  • #インフラ
開発eurekapu-nuxt4メモ

音声ファイル管理基盤の整備

前日にナレーション機能を組み上げて音声ファイルが一気に増えた。152ファイルがpublic/audio/に居座り、git statusを叩くたびに差分が画面を埋め尽くした。このまま放置するとリポジトリが膨張して開発速度を落とすので、音声ファイルの管理基盤を一日がかりで整備した。

public/audio/ から audio-assets/ への移行

まずファイルの置き場を変えた。public/audio/ にあった152個の音声ファイルをプロジェクトルートの audio-assets/ に移動し、.gitignore に audio-assets/ を追加してリポジトリの追跡対象から外した。

audio-assets/
├── elevenlabs/   # ElevenLabs生成音声
├── gcp/          # Google Cloud TTS (Chirp 3 HD)
└── voicevox/     # VOICEVOX生成音声

これで git add . しても音声ファイルがステージングに乗らなくなった。

dev server middleware の作成

音声をpublicから外したので、ローカル開発時に音声が404になる。この問題を dev-audio.ts ミドルウェアで解決した。

// server/middleware/dev-audio.ts
// /audio/ へのリクエストを audio-assets/ から配信

開発サーバー起動中のみ動作し、本番ビルドには含まれない。defineEventHandler でリクエストパスを見て、audio-assets/ ディレクトリから該当ファイルを返す仕組み。

runtimeConfig に audioBaseUrl を追加

nuxt.config.ts の runtimeConfig.publicaudioBaseUrl を追加した。

環境解決先
開発"" (空文字)localhost + dev middleware
本番R2 CDNのURLCloudflare R2

環境変数 NUXT_PUBLIC_AUDIO_BASE_URL で本番URLを注入する。Cloudflare Pagesの環境変数として設定した。

useAudioUrl composable

音声URLの組み立てロジックが各コンポーネントに散らばっていたので、useAudioUrl composableに集約した。

const { resolveUrl } = useAudioUrl()
const src = resolveUrl('/audio/elevenlabs/chapter01/scene01.wav')

audioBaseUrl が空なら相対パス、値があればCDNのフルURLを返す。呼び出し側はどの環境で動いているか意識しなくてよい。

narrationChapter01.ts のパス統一

台詞データファイルの narrationChapter01.ts では、音声ファイルのパスがハードコードされていた。/audio/elevenlabs/chapter01/prologue_001.wav のような文字列が55行並んでいた状態。

AUDIO定数を定義してテンプレートリテラルに書き換えた。

const AUDIO = {
  ELEVENLABS: '/audio/elevenlabs/chapter01',
  VOICEVOX: '/audio/voicevox/chapter01',
} as const

// Before: "/audio/elevenlabs/chapter01/prologue_001.wav"
// After:  `${AUDIO.ELEVENLABS}/prologue_001.wav`

パスのプレフィックスが1箇所に集まったので、ディレクトリ構造を変えても定数の値を直すだけで済む。

Cloudflare R2 へのアップロード

ElevenLabs音声59ファイルとVOICEVOX音声をR2バケットにアップロードした。

--remote フラグの罠

wrangler r2 object put を実行して「アップロード完了」と表示されたのに、本番のR2バケットにファイルが見つからない。ダッシュボードを何度リロードしても0 objects。

原因は --remote フラグの付け忘れだった。wranglerはデフォルトでローカルのR2エミュレータに書き込む。ローカルの .wrangler/state/ にファイルが溜まっているのを見つけて気づいた。

# NG: ローカルエミュレータに書き込まれる
wrangler r2 object put bucket/path/file.wav --file=./file.wav

# OK: 本番R2に書き込まれる
wrangler r2 object put bucket/path/file.wav --file=./file.wav --remote

前日のR2アップロードでも同じミスをしていた。2日連続で踏んだので、もう忘れない。

環境変数の設定

Cloudflare Pagesのプロジェクト設定から NUXT_PUBLIC_AUDIO_BASE_URL を設定した。値はR2バケットのカスタムドメインURL。

これでデプロイ時にNuxtがruntimeConfigにCDN URLを注入し、useAudioUrl経由で全コンポーネントがR2から音声を取得する。

DEPLOY.md の作成

音声ファイルのアップロード手順、環境変数の設定、デプロイコマンドをDEPLOY.mdにまとめた。次に音声を追加するときの手順書として残した。

主な記載内容:

  • R2への音声アップロードコマンド(--remote フラグ付き)
  • Cloudflare Pages環境変数の一覧
  • pnpm deploy:cloudflare の実行手順
  • audio-assets/ のディレクトリ構成ルール

学んだこと

  • wranglerのデフォルトはローカル: r2 object putd1 execute も、--remote なしだとローカルエミュレータに向く。「成功した」と表示されても本番には届いていない。コマンドの出力を鵜呑みにせず、ダッシュボードで実物を確認する癖をつける
  • 音声ファイルはGit管理しない: 152ファイル・数十MBのバイナリがリポジトリに入ると、cloneが遅くなり、diffが見づらくなり、CI/CDの転送量が増える。最初から外部ストレージに置くのが正解
  • URLの解決層を1つ挟む: composableで環境差分を吸収しておくと、配信元がR2からS3に変わっても呼び出し側は無傷

今日の構成変更まとめ

変更BeforeAfter
音声ファイル配置public/audio/ (Git追跡)audio-assets/ (.gitignore)
ローカル配信Nuxtのpublic配信dev-audio.ts middleware
本番配信public配信Cloudflare R2 CDN
URL解決ハードコードuseAudioUrl composable
パス定義文字列リテラル55行AUDIO定数 + テンプレートリテラル
手順書なしDEPLOY.md