Skip to content

Instantly share code, notes, and snippets.

@mono0926
Last active March 5, 2026 11:22
Show Gist options
  • Select an option

  • Save mono0926/266afd3a879d7719ec02ec127adf41e8 to your computer and use it in GitHub Desktop.

Select an option

Save mono0926/266afd3a879d7719ec02ec127adf41e8 to your computer and use it in GitHub Desktop.
Flutter Web のキャッシュ最適化戦略 (Firebase Hosting)

Flutter Web のキャッシュ最適化戦略 (Firebase Hosting)

Flutter WebアプリをFirebase Hostingで配信する際の、最適なキャッシュ制御(Cache-Control)に関するドキュメントです。
※Flutter 3.22以降前提

考えられるキャッシュ戦略の選択肢と比較

Flutter Webにおけるキャッシュ制御には、主に以下の3つのアプローチが考えられます。

1. 手法A: Service Worker + エントリポイントのEtag検証 (✅ 本プロジェクトで採用)

エントリポイントとなるファイル(index.html, *.json, flutter_service_worker.js, flutter_bootstrap.js など)のみに no-cache を付与し、サーバーに必ず変更有無(Etag)を問い合わせさせます。

  • メリット: Flutter標準のビルドシステムとService Workerの機能に完全に乗っかっているため、設定がシンプルで保守コストが極めて低いです。
  • デメリット: Service Workerの「バックグラウンドで新しいバージョンをダウンロードし、次回の起動時に適用する」という仕様により、強制アップデート画面を出している場合でも「1回目に古い画面が一瞬表示され、リロードして初めて新しい画面になる」というラグが発生することがあります。

2. 手法B: ファイル名ハッシュ化 (Aggressive Cache Busting + Immutable)

ビルド終了後にカスタムスクリプトを走らせ、main.dart.js 等を main.dart.[ハッシュ].js にリネームし、index.html のパスも動的に書き換えます。実体ファイルには Cache-Control: max-age=31536000, immutable を指定します。

  • メリット: キャッシュ不整合が物理的に発生しなくなり、1発目で必ず最新のアプリが立ち上がります。
  • デメリット: ビルド手順が複雑化(パッチスクリプトのメンテが必要)し、Flutterのコア仕様(ブートストラップ等の構造)が変わった際にスクリプトが壊れて直す手間が発生します。

3. 手法C: クエリパラメータによる簡易キャッシュバスティング

index.html などをビルド後に自動修正し、<script src="main.dart.js?v=1.0.1"> のように ?v= を付与します。

  • メリット: 手法Bよりもカスタムスクリプトが簡単です。
  • デメリット: プロキシサーバーや一部のブラウザ設定によってクエリアギュメントが無視され、強固なキャッシュが残存する(手法BやAほどの確実性はない)リスクがあります。

結論

機能の保守性とFlutterフレームワークの進化への追従性を考慮すれば、「手法A」がベストの落とし所です。 下手にビルドスクリプトを複雑にして手法Bを取り入れると、今後のFlutterのアップデートでビルドフローが壊れるリスクの方が大きいため、本プロジェクトではFlutterチームが想定している標準的な運用(手法A)を採用しています。 さらに、最新のFlutter仕様に合わせ、flutter_bootstrap.jsno-cacheの対象に含めることで、ラグや不具合を最小化する微調整を行っています。


本プロジェクトの firebase.json 設定

上記の手法Aの考え方に基づき、firebase.json にて、対象のFirebase Hostingサイトに対して以下のヘッダー設定を行なっています。 ※ モダンなFlutter Web (v3.22以降) で使われる flutter_bootstrap.js も検証対象に含めています。

"headers": [
  {
    "source": "**/*.@(html|json|wasm|mjs)",
    "headers": [
      {
        "key": "Cache-Control",
        "value": "no-cache"
      }
    ]
  },
  {
    "source": "@(flutter_service_worker.js|flutter_bootstrap.js)",
    "headers": [
      {
        "key": "Cache-Control",
        "value": "no-cache"
      }
    ]
  }
]

期待される挙動とその理由

1. no-cache 指定(index.html, *.json, flutter_service_worker.js, flutter_bootstrap.js

挙動: no-cache は「キャッシュを保存しない」という意味ではなく、「キャッシュを使う前に、必ずサーバー(Firebase Hosting)へ変更がないか問い合わせる(Etag検証)」 という意味です。

理由:

  • アプリへのアクセス時、ブラウザはまず index.html やブートストラップのスクリプト群を読み込みます。
  • サーバー側でファイルに変更がなければ、サーバーからは 304 Not Modified(データ本体なし)が即座に返されます。この通信は軽量で一瞬で終わります。
  • 変更があった場合のみ、新しいファイルがダウンロードされます。
  • これにより、「最新版があるかどうかのチェック」を確実かつ高速に行うことができます。

2. キャッシュ制限なし(main.dart.js 等の巨大なファイル)

挙動: 上記以外の *.js などの実体ファイルには Cache-Control ヘッダーを上書き指定せず、デフォルトのキャッシュ挙動を許容します。ブラウザは一度ダウンロードしたファイルを強力にキャッシュして使い回します。

理由:

  • エントリポイントの検証で「更新あり」と判定され、新しい flutter_service_worker.js が読み込まれると、Service Workerは内部に持っている新しいファイルのハッシュ値(例: main.dart.js ではなく一意のハッシュキー)に基づいて新しい実体ファイルだけをダウンロードします。
  • 更新がない通常アクセスの場合は、各ブラウザがローカルにキャッシュしている数MBのJS/WASMファイルをそのまま使うため、無駄な通信が発生せず爆速でアプリが起動します。

NGな設定例: すべての実体ファイルに no-storemust-revalidate を付与する

// 【非推奨】絶対にやってはいけない設定
"source": "**/*.@(html|js)",
"headers": [
  {
    "key": "Cache-Control",
    "value": "no-cache, no-store, must-revalidate"
  }
]

なぜダメなのか: no-store を付与すると、ブラウザはキャッシュを一切保存しなくなります。 その結果、ユーザーが複数回アクセスするたびに、毎回数MBの main.dart.js 等の実体データをゼロからダウンロードすることになり、通信量の増大や、起動が毎回遅くなるという致命的なUX悪化を招きます。 必ず上述のような「役割を分けた」キャッシュ戦略を採用してください。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment