#はじめに

「キャッシュを長く設定したいけど、更新したときに古い内容が表示されるのは困る」——これはWeb開発者の共通の悩みです。

キャッシュバスティングは、この問題を解決するための戦略です。ファイルの内容が変わったときに、確実に新しいバージョンを取得させる仕組みを作ります。

この記事を読むと、以下のことができるようになります:

  • キャッシュバスティングの必要性を理解できる
  • ファイル名ハッシュの仕組みがわかる
  • ビルドツールとの連携方法がわかる

#キャッシュのジレンマ

#短いキャッシュ期間

Cache-Control: max-age=60
  • ✅ 更新がすぐ反映される
  • ❌ 毎分サーバーに確認が必要
  • ❌ パフォーマンスが悪い

#長いキャッシュ期間

Cache-Control: max-age=31536000
  • ✅ 高速(1年間キャッシュ)
  • ❌ 更新しても古い内容が表示される
  • ❌ ユーザーに手動でキャッシュクリアを依頼?

#解決策: キャッシュバスティング

内容が変わったらURLも変えるという発想です。

# v1
/assets/app.a1b2c3.js

# v2(内容が変わった)
/assets/app.d4e5f6.js  ← URLが変わるので、新しくダウンロード

#ファイル名ハッシュ

ファイル名にコンテンツのハッシュ値を含める方法です。

#仕組み

ファイル内容: console.log("Hello World");
    ↓ ハッシュ計算
ハッシュ値: a1b2c3d4
    ↓ ファイル名に付与
app.a1b2c3d4.js

内容が1文字でも変わると、ハッシュ値が完全に変わります。

#メリット

  • 内容が同じならURLも同じ(キャッシュが効く)
  • 内容が違えばURLも違う(新しいファイルを取得)
  • 長いキャッシュ期間を設定できる

#Cache-Controlの設定

Cache-Control: max-age=31536000, immutable

1年間キャッシュ。immutableにより、リロード時も再検証をスキップ。

#HTMLファイルの扱い

重要: HTMLファイル自体はハッシュを含めません。

# これはダメ
index.a1b2c3.html ← URLが変わってしまう

# これが正解
index.html ← 固定URL
  └→ app.a1b2c3.js を参照

HTMLファイルは常に最新を取得し、その中で参照するアセットのURLにハッシュを含めます。

Cache-Control: no-cache

#全体の構成

index.html (no-cache, 常に最新を確認)
  ├── /assets/app.a1b2c3.js (max-age=1年)
  ├── /assets/style.d4e5f6.css (max-age=1年)
  └── /assets/logo.g7h8i9.png (max-age=1年)

#ビルドツールとの連携

#webpack

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
  },
};

生成されるファイル:

main.a1b2c3d4e5f6g7h8.js
vendor.i9j0k1l2m3n4o5p6.js

#Vite

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },
  },
};

#Next.js

デフォルトでファイル名ハッシュが有効です。

/_next/static/chunks/pages/index-a1b2c3.js

#クエリパラメータ方式

URLは変えず、クエリパラメータでバージョンを指定する方法です。

<script src="/app.js?v=1.2.3"></script>
<script src="/app.js?v=1.2.4"></script>  <!-- バージョンアップ -->

#メリット

  • 既存のファイル名を変えなくて済む
  • シンプルに実装できる

#デメリット

  • 一部のCDNはクエリパラメータを無視する設定になっている場合がある
  • キャッシュキーの扱いがCDNによって異なる

#使用が適切な場面

  • ファイル名ハッシュを導入できない古いシステム
  • 手動でバージョン管理する小規模サイト

#マニフェストファイル

ビルドツールは「どのファイルがどのハッシュ付きファイル名になったか」を記録したマニフェストを生成します。

// manifest.json
{
  "main.js": "main.a1b2c3.js",
  "style.css": "style.d4e5f6.css"
}

サーバーサイドレンダリングやテンプレートエンジンで、このマニフェストを参照してHTMLを生成します。

<script src="<?= $manifest['main.js'] ?>"></script>

#実践的な設定例

#静的サイト(nginx)

# HTMLファイル
location ~* \.html$ {
    add_header Cache-Control "no-cache";
}

# ハッシュ付きアセット
location ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|svg|woff2)$ {
    add_header Cache-Control "max-age=31536000, immutable";
}

# ハッシュなしアセット(フォールバック)
location ~* \.(js|css|png|jpg|svg|woff2)$ {
    add_header Cache-Control "max-age=86400";
}

#CDN設定(Cloudflare)

Page Rulesまたは Transform Rulesで:

  • *.htmlCache-Control: no-cache
  • /assets/*Cache-Control: max-age=31536000, immutable

#チャンク分割との組み合わせ

大きなJavaScriptファイルを複数のチャンクに分割することで、変更のない部分はキャッシュを活用できます。

# 変更前
main.a1b2c3.js (全体)

# チャンク分割後
vendor.x1y2z3.js (ライブラリ、ほぼ変更なし) → キャッシュ継続
main.a1b2c3.js (アプリコード) → 変更があれば新しいハッシュ

ライブラリを更新しない限り、vendor.jsのキャッシュは有効なまま。

// webpack
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendor',
      },
    },
  },
}

#デバッグとトラブルシューティング

#「更新したのに反映されない」場合

  1. ブラウザキャッシュを確認: ハードリロード(Ctrl+Shift+R)
  2. CDNキャッシュを確認: パージを実行
  3. HTMLファイルのキャッシュを確認: no-cacheが設定されているか
  4. ビルドが成功しているか確認: 新しいハッシュが生成されているか

#DevToolsでの確認

Networkタブで:

  • ファイル名にハッシュが含まれているか
  • Cache-Controlヘッダーが正しく設定されているか
  • Status(200 vs 304 vs from cache)

#まとめ

  • キャッシュバスティングは「内容が変わったらURLも変える」戦略
  • ファイル名ハッシュが最も確実な方法
  • HTMLはno-cache、アセットはmax-age=1年+immutable
  • ビルドツール(webpack、Vite等)が自動でハッシュを生成
  • マニフェストファイルで元のファイル名との対応を管理
  • チャンク分割でさらに効率的なキャッシュ活用

#次のステップ

キャッシュバスティングを理解したところで、次はService Workerとキャッシュについて学びましょう。Service Workerを使うと、オフライン対応や、より柔軟なキャッシュ戦略を実装できます。

#参考リンク