HTTPキャッシュヘッダー キャッシュコントロール、ETAG、およびmax-age。推測なし
ウェブ開発者向けHTTPキャッシュヘッダーの実用ガイド:Cache-Controlディレクティブが実際にどう機能するか、ETagが304リバリデーションをトリガーする仕組み、デプロイに耐えるTTLの選定方法、そしてキャッシュが問題を引き起こす誤りについて。
送信するすべてのHTTPレスポンスは、キャッシュされるか、あるいはキャッシュされないかのどちらかです。意識的に設定していない場合、ブラウザがその判断を下します。その結果、頻繁に変化するコンテンツに対しては強力なキャッシュが行われ、ほとんど変化しないコンテンツに対してはキャッシュが行われません。どちらのケースもユーザーにとって悪影響を及ぼします。
このガイドは、HTTPキャッシュヘッダーを正しく設定するためのメンタルモデルを提供します。各ディレクティブが制御する内容、ETagが再検証をトリガーする仕組み、およびデプロイメント中にステートレスなコンテンツを提供するためのTTL(Time-To-Live)をどのように選ぶかについてです。
ブラウザキャッシュが実際にどのように機能するか
ブラウザがリソースを要求する際、まずローカルキャッシュを確認します。新しいキャッシュコピーが存在する場合、そのコピーを即座に提供し、ネットワークリクエストは一切行われません。キャッシュコピーが古くなっている可能性がある場合、ブラウザは 条件付きリクエスト をオリジンサーバーに送信します。オリジンは、リソースが変更されていないことを確認(304 Not Modified)するか、または更新されたレスポンスを送信(200 OK)します。
CDNはこのライフサイクルの中央に位置しています。ユーザーの地理的に近い場所にレスポンスをキャッシュし、同じHTTPキャッシュヘッダーを遵守しています。ただし、CDN固有の拡張機能があるため、例えば s-maxage.
キャッシュ動作を決定する3つの質問は以下の通りです:
- このレスポンスはキャッシュ可能ですか? を制御しています
Cache-Control: no-storeまたはprivate - どれだけ新鮮ですか? を制御しています
max-ageまたはs-maxage - 古さの検証はどのように行われますか? をETagまたは
Last-Modified
Cache-Controlヘッダーのディレクティブ
の Cache-Control ヘッダーはキャッシュポリシーを宣言する主な方法です。複数のディレクティブはコンマで区切られています。それぞれのディレクティブが実際にどのように機能するかを以下に示します。
max-age
max-age=N キャッシュ(ブラウザおよびCDN)がレスポンスが新鮮である期間を示します。受信された時点からちょうど24時間間、レスポンスは新鮮と見なされます。その後、キャッシュは再検証を実行してから再度提供する必要があります。 max-age=86400 は、受信された時点からちょうど24時間間、レスポンスが新鮮と見なされます。その後、キャッシュは再検証を実行してから再度提供する必要があります。
静的アセット(例: main.abc123.js)に対して、1年間のTTLは一般的です: max-age=31536000HTMLドキュメントに対しては、非常に短い期間、あるいはまったくキャッシュしない方が安全です。なぜなら、HTMLはそのバージョン付きアセットを参照しているからです。
s-maxage
s-maxage を共有キャッシュ(CDN、プロキシサーバー)のみに上書きします。ブラウザは無視します。これにより、ユーザーに長期間キャッシュされたレスポンスを提供しつつ、CDNのエッジキャッシュをより新鮮に保つことができます。典型的なパターンは max-age 共有キャッシュ(CDN、プロキシサーバー)用にのみ適用。ブラウザはこれを無視します。これにより、ユーザーに長期間キャッシュされた応答を提供しつつ、CDNのエッジキャッシュをより新鮮に保つことができます。典型的なパターンは Cache-Control: public, max-age=3600, s-maxage=86400 — ブラウザは1時間、CDNは24時間キャッシュします。
no-cache
no-cache 「キャッシュしない」という意味ではありません。キャッシュは再検証を行い、保存されたレスポンスを提供する必要があります。レスポンスはキャッシュされていますが、各使用時にオリジンにリクエストを送る必要があります。これは頻繁に変化するコンテンツに対して、304レスポンスによる帯域使用の節約が得られる場合に適しています。
no-store
no-store が唯一、キャッシュを完全に防止するディレクティブです。ブラウザキャッシュ、CDNキャッシュ、ディスク書き込みも一切行われません。センシティブなユーザーデータ(銀行明細、医療記録、トークン)を含むレスポンスに使用してください。デフォルトで使用しないでください。なぜなら、キャッシュの考慮がまだ行われていないからです。
public と private
public 共有キャッシュ(CDN)がレスポンスをキャッシュできるように明示的に許可しています。ただし、リクエストが Authorization ヘッダーに配置することによって機能します。 private の場合、キャッシュはユーザーのブラウザにのみ制限されます。CDNはそのレスポンスをキャッシュしてはなりません。ユーザー特定の認証レスポンスの場合、 private は、1人のユーザーのレスポンスが別のユーザーに提供されないことを防ぎます。
must-revalidate
must-revalidate キャッシュがオリジンにアクセスできない場合、古くなったレスポンスを提供しないようにします。その結果、ネットワークが不可用の場合、キャッシュは期限切れのレスポンスを提供する可能性があります。このディレクティブにより、オリジンがアクセスできない場合、リクエストは504エラーで失敗します。データが古くなった場合よりもエラーの方が悪影響を及ぼすコンテンツに適しています。
ETag:正確な再検証
ETagは、リソースの特定バージョンに対するサーバー生成識別子です。これはレスポンスボディの「指紋」と考えられます。サーバーはレスポンスにそれを送信します:
ETag: "abc123def456"
キャッシュコピーが期限切れになると、ブラウザは保存されたETagを含む条件付きGETリクエストを送信します:
If-None-Match: "abc123def456"
リソースが変更されていない場合、サーバーは 304 Not Modified を返し、空のボディを返します。これにより帯域使用を節約しながらも、新鮮さを確認できます。変更された場合、サーバーは 200 OK を返し、新しいETagを返します。
強力なETagと弱力なETag
あ 強力なETag ("abc123")は、レスポンスがバイトごとに完全に一致することを意味します。一方、 弱力なETag (W/"abc123")は、レスポンスが意味的に同等であるが、空白やヘッダー順序などの微細な違いを含む可能性があります。弱力なETagは効率的に生成できますが、範囲リクエストでは使用できません。特定の理由がない限り、強力なETagを使用してください。
Last-Modified:古い代替方法
ETag以前、サーバーは Last-Modified タイムスタンプを使用して再検証を行いました。サーバーは次のようになります:
Last-Modified: Thu, 01 May 2026 12:00:00 GMT
ブラウザは次のようになります:
If-Modified-Since: Thu, 01 May 2026 12:00:00 GMT
サーバーは、そのタイムスタンプ以降に変更されていない場合、304を返します。
弱点:タイムスタンプは1秒の粒度を持っています。1秒内に2回変更されたファイルは、変更されていないように見えます。ETagはこれを正しく処理し、優先されます。現代のフレームワークは、通常、両方を送信します。ブラウザは利用可能なものを選択し、ETagが優先されます。
デプロイメントに影響を与えないキャッシュ破壊
静的アセットに対して長い max-age が安全であるためには、コンテンツが変更されたときにURLが変更される必要があります。2つの戦略があります:
URLフィンガープリント(推奨)
ファイルコンテンツのハッシュをファイル名に含めます: main.a1b2c3d4.js。ファイルが変更されたとき、ハッシュが変更され、URLが変更され、ブラウザは新しいファイルを取得します。古いURLはキャッシュに残りますが、HTMLが新しいURLを参照した後は再び要求されません。
Webpack、Vite、およびほとんどの現代のバンドラーは自動的にこれを実行します。このパターンにより、 Cache-Control: public, max-age=31536000, immutable を設定できます。 immutable ディレクティブは、キャッシュエントリが技術的に古くなった場合でも、再検証を行う必要がないようにします。
クエリ文字列(信頼性が低い)
URLにバージョンをクエリ文字列として追加(main.js?v=1.2.3)は、技術的には異なるURLを生成しますが、一部のCDNやプロキシはクエリ文字列をキャッシュキーの構築時に無視します。パス内のURLフィンガープリントの方がより信頼性があり、広くサポートされています。
キャッシュを損なう一般的な誤り
キャッシュすべきでないAPIレスポンスをキャッシュする
ユーザー特定または時間依存のデータを返すJSON APIは、 Cache-Control: no-store または少なくとも private, no-cacheを使用すべきです。一般的な誤りは、CDNがレスポンスをキャッシュし、1人のユーザーのデータを別のユーザーに提供してしまうことです。APIが /api/user/profile かつ、別のユーザーのデータを提供することです。APIがこの値を設定していない場合、CDNはgzipバージョンをキャッシュし、gzipをサポートしないクライアントにそれを提供する可能性があります——あるいは逆に、gzipをサポートするクライアントにgzipバージョンを提供する可能性があります。圧縮が条件付きの場合、常にこの値を設定してください。 Cache-Control ヘッダーを設定していない場合、一部のCDNはヒューリスティクスを使って自動的にキャッシュします。
Vary: Accept-Encodingを忘れる
サーバーがクライアントの Accept-Encoding ヘッダーに応じて圧縮版と非圧縮版の両方を提供する場合、キャッシュはそれぞれのバリエーションを別々に保存する必要があります。Vary: Accept-Encodingがなければ、CDNはgzip版をキャッシュし、gzipをサポートしないクライアントに提供する可能性があります(逆も同様)。圧縮が条件付きの場合、常にこれを設定してください。 Vary: Accept-Encoding、CDNがgzipバージョンをキャッシュし、gzipをサポートしないクライアントにそれを提供する——あるいは逆に、gzipをサポートするクライア的にgzipバージョンを提供する可能性があります。圧縮が条件付きの場合、常にこの値を設定してください。
no-cacheを使用するが、no-storeを意図している
開発者は、キャッシュを完全に防止したい場合に Cache-Control: no-cache を書くことがありますが、 no-cache は、レスポンスを保存しているものの、各使用時に再検証を要求します。本当に保存をしない場合にのみ、 no-store を使用してください。
HTMLにキャッシュバスタ戦略がない場合にmax-ageを設定する
HTMLドキュメントはバージョン付きアセットを参照しています。HTMLを長期間の max-ageでキャッシュすると、ユーザーはデプロイ後に新しいアセットファイル名を取得できず、古いHTMLを実行し続けます。これは古いハッシュを参照しているためです。HTMLに対して短いTTL(または no-cache)を設定し、HTMLが参照する不変アセットに対して長期間TTLを設定してください。
配信前にエクスパイアタイムを計算する
TTLの値を選ぶのは、壁時計時間で意味を視覚化できる場合が簡単です。iotools.cloudの max-age は、秒単位でTTLを入力し、正確なエクスパイア時刻を表示します。86400(24時間)、2592000(30日)、31536000(1年)などの値をサーバー設定やCDNルールに適用する前に、確認に使用できます。 HTTPキャッシュTTL / max-age計算ツール iotools.cloudでは、秒単位のTTLを入力し、正確な有効期限時刻を確認できます。86400(24時間)、2592000(30日)、31536000(1年)などの値をサーバー設定やCDNルールに反映する前に、これらの値を確認するのに役立ちます。
実用的なキャッシュポリシーチェックリスト
- HTMLドキュメント:
Cache-Control: no-cache— 常に再検証を行い、何も変更されていない場合に304レスポンスを活用する - バージョン付き静的アセット(JS、CSS、ファイル名にハッシュを含む画像):
Cache-Control: public, max-age=31536000, immutable - 未バージョン付き静的アセット(フォント、favicon):
Cache-Control: public, max-age=604800(1週間) - パブリックで時間依存のAPIレスポンス:
Cache-Control: public, max-age=60, s-maxage=300— ブラウザ用の短いTTL、CDN用の長いTTL - ユーザー特定のAPIレスポンス:
Cache-Control: private, no-cache - センシティブなデータ:
Cache-Control: no-store - 常に
Vary: Accept-Encodingを設定する(圧縮レスポンスを条件付きで提供する場合)
ETagは、キャッシュするすべてのコンテンツに対してデフォルトで有効です。これは再検証を帯域効率的に実行するためのメカニズムです。ほとんどのウェブサーバー(Nginx、Apache、Caddy)は、ETagを自動的に生成しており、無効にしない限りです。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
