テックブログ

HTTPキャッシュ入門:Cache-ControlとETagの違いを理解する

HTTPキャッシュ入門:Cache-ControlとETagの違いを理解する

HTTPキャッシュは、ブラウザや中間サーバーが一度取得したレスポンスを再利用し、通信量を減らしたり表示速度を改善したりする仕組みです。Cache-Control は「どれくらい保存してよいか」「再利用してよいか」といったキャッシュ方針を指定し、ETag はサーバー側の内容が変わったかを確認するために使われます。キャッシュは悪者ではありませんが、静的ファイルとAPIレスポンスでは適した設定が違うため、役割を分けて理解することが大切です。

1. HTTPキャッシュとは何か

1-1. レスポンスを再利用して通信を減らす仕組み

HTTPキャッシュとは、一度取得したレスポンスをブラウザや中間サーバーが保存し、次回以降に再利用する仕組みです。毎回サーバーへ取りに行かなくて済むため、通信量を減らし、表示を速くできます。

たとえば、同じ画像やCSSファイルをページ表示のたびに毎回ダウンロードすると、無駄な通信が増えます。キャッシュが効いていれば、ブラウザは保存済みのレスポンスを使えるため、ページ表示が軽くなります。つまり、HTTPキャッシュはパフォーマンス改善のために重要な仕組みです。

ただし、キャッシュは「保存して再利用する」仕組みなので、設定を間違えると古い内容が表示されることがあります。キャッシュで詰まったときは、「保存してよいか」「いつまで使ってよいか」「変更確認をしているか」を分けて見ると整理しやすいです。

1-2. ブラウザキャッシュと中間キャッシュ

HTTPキャッシュには、ブラウザ内に保存されるキャッシュと、CDNやプロキシのような中間キャッシュがあります。どこに保存されるかで注意点が変わります。

ブラウザキャッシュは、ユーザーの端末内に保存されるキャッシュです。一方、中間キャッシュはCDNやリバースプロキシなどがレスポンスを保存し、複数ユーザーへ再利用することがあります。静的ファイルのように誰が見ても同じ内容なら中間キャッシュと相性がよいですが、ユーザーごとに違う情報を含むレスポンスでは注意が必要です。

たとえば、ロゴ画像やバージョン付きのJSファイルは中間キャッシュしやすいです。一方、マイページのユーザー情報を共有キャッシュしてしまうと、別ユーザーへ見えてはいけない情報が返る危険があります。キャッシュ設定では、「誰に対して同じ内容か」を必ず考える必要があります。

1-3. 更新したのに反映されない理由

「更新したのに反映されない」原因の多くは、ブラウザやCDNが古いレスポンスをまだ有効だと判断していることです。サーバー側のファイルを更新しても、クライアントが取り直すとは限りません。

HTTPキャッシュでは、Cache-Controlmax-age などで「この期間は再利用してよい」と指定できます。その期間中は、ブラウザがサーバーへ確認せずに手元のキャッシュを使うことがあります。つまり、サーバーには新しいファイルがあっても、ブラウザは古いファイルを使い続けることがあります。

よくある対策は、CSSやJSのファイル名にハッシュを含めることです。たとえば app.abc123.js のように内容が変わるとファイル名も変わる設計にすると、古いキャッシュと新しいファイルを区別しやすくなります。キャッシュを消すのではなく、更新時に別リソースとして扱わせるのが安全な考え方です。

2. Cache-Controlの基本

2-1. max-ageで保存期間を決める

max-age は、レスポンスを何秒間キャッシュとして再利用してよいかを指定する設定です。Cache-Controlの中でも最も基本的な指定のひとつです。

たとえば max-age=3600 なら、レスポンス取得後3600秒、つまり1時間は新鮮なキャッシュとして扱えます。この期間内であれば、ブラウザはサーバーへ再確認せず、保存済みレスポンスを使うことがあります。通信を減らせる一方で、更新がすぐ反映されにくくなる点には注意が必要です。

Cache-Control: public, max-age=3600

この例では、レスポンスを1時間キャッシュ可能にしています。注意点は、頻繁に変わるAPIレスポンスへ長い max-age を付けると、ユーザーが古い情報を見続ける可能性があることです。保存期間は、リソースの更新頻度に合わせて決める必要があります。

2-2. no-storeとno-cacheの違い

no-storeno-cache は名前が似ていますが、役割は違います。特に no-cache を「キャッシュしない」と理解すると混乱しやすいです。

no-store は、レスポンスを保存しないように指示します。認証情報や個人情報を含むレスポンスでは重要です。一方、no-cache は保存自体を禁止するのではなく、再利用する前にサーバーへ確認するように指示します。つまり、no-cache は「使う前に再検証して」という意味に近いです。

Cache-Control: no-store
Cache-Control: no-cache

この2つは似て見えますが、選び方を間違えると期待と違う動きになります。ログイン後の個人情報ページなど、保存されること自体を避けたい場合は no-store を検討します。更新確認をしたうえで再利用したい場合は no-cache が候補になります。

2-3. publicとprivateの使い分け

publicprivate は、レスポンスを誰のキャッシュに保存してよいかを示す指定です。中間キャッシュを意識すると重要になります。

public は、ブラウザだけでなくCDNや共有プロキシのような中間キャッシュでも保存してよいことを示します。画像やCSS、JSなど、誰が取得しても同じ内容なら使いやすいです。private は、ユーザーごとのブラウザキャッシュには保存できても、共有キャッシュには保存させたくない場合に使います。

Cache-Control: private, max-age=300

この例では、ユーザーのブラウザ内では短時間キャッシュできますが、共有キャッシュ向けではないことを示しています。注意点は、ユーザーごとに異なるレスポンスへ安易に public を付けないことです。個人情報や認証状態に依存する内容は、共有キャッシュに乗せるべきではありません。

3. ETagと304 Not Modified

3-1. ETagはレスポンス内容の識別子

ETag は、レスポンス内容のバージョンや状態を表す識別子です。ブラウザはETagを使って、前回取得した内容と今の内容が同じかを確認できます。

たとえば、サーバーがレスポンスに ETag: "abc123" を付けて返すと、ブラウザは次回以降その値を使って「この内容はまだ同じですか」と確認できます。内容が変わっていなければ、サーバーは本文を返さずに済みます。つまり、ETagはキャッシュの保存期間を決めるものではなく、内容の変化を確認するための手がかりです。

ETag: "user-list-v1"

この例では、レスポンスに識別子を付けています。注意点は、ETagの作り方が雑だと、内容が変わっていないのに変わった扱いになったり、逆に変わったのに同じ扱いになったりすることです。ETagは「内容が同じか」を判断できるように設計する必要があります。

3-2. If-None-Matchと再検証の流れ

If-None-Match は、ブラウザが前回受け取ったETagをサーバーへ送り、内容が変わったか確認するためのリクエストヘッダーです。これを使った流れを再検証と呼びます。

まずサーバーは、レスポンスにETagを付けて返します。次回、ブラウザは If-None-Match にそのETagを入れてリクエストします。サーバーは現在の内容と比較し、変わっていなければ本文を返さず 304 Not Modified を返します。変わっていれば、新しい本文と新しいETagを返します。

If-None-Match: "user-list-v1"

この例では、ブラウザが前回のETagを使って再確認しています。注意点は、再検証が発生するとサーバーへの通信自体は行われることです。本文ダウンロードは省けますが、完全に通信ゼロになるわけではありません。

3-3. 304 Not Modifiedが返る意味

304 Not Modified は、サーバー側の内容が前回と変わっていないため、手元のキャッシュを使ってよいという意味です。エラーではありません。

ブラウザがETagやLast-Modifiedを使って再検証した結果、サーバーが「変更なし」と判断すると304を返します。このとき、通常はレスポンス本文を返しません。ブラウザは保存済みの本文を再利用します。つまり、304は通信量を減らしつつ、内容の新しさも確認するための仕組みです。

初心者がDevToolsで304を見ると「APIが失敗したのでは」と感じることがありますが、基本的には正常なキャッシュ動作です。ただし、更新したのに304が返り続けるなら、ETagやLast-Modifiedの更新条件が正しいかを確認する必要があります。

4. 静的ファイルとAPIのキャッシュ方針

4-1. CSS/JS/画像は長めにキャッシュしやすい

CSS、JS、画像のような静的ファイルは、ファイル名にバージョンやハッシュを含めれば長めにキャッシュしやすいです。更新時にURLが変わるため、古いキャッシュと衝突しにくくなります。

たとえば、ビルド時に app.a1b2c3.js のような名前を付ければ、内容が変わったときにファイル名も変わります。そのため、古いファイルは長くキャッシュしても、新しいリリースでは別URLとして取得できます。これはWebフロントエンドでよく使われるキャッシュ戦略です。

Cache-Control: public, max-age=31536000, immutable

この例では、長期間キャッシュし、内容が変わらない前提で扱う設定です。注意点は、同じURLで内容を差し替える運用には向かないことです。長期キャッシュを使うなら、ファイル名を変える仕組みとセットで考える必要があります。

4-2. APIレスポンスは内容と要件で慎重に決める

APIレスポンスのキャッシュは、静的ファイルより慎重に決める必要があります。返す内容がユーザーや時間によって変わることが多いからです。

たとえば、商品一覧のように少し古くても許容できるデータは短時間キャッシュできるかもしれません。一方、在庫数、通知件数、マイページ情報、決済状態のように最新性や個人性が重要なレスポンスは、安易にキャッシュすると問題になります。APIでは「速くしたい」だけでなく、「古い値を見せてもよいか」を必ず考える必要があります。

実務では、APIごとにキャッシュ方針を分けるのが基本です。すべてのAPIに同じ Cache-Control を付けるのではなく、公開情報か、個人情報か、どれくらい古くても許されるかを見て決めると安全です。

4-3. 認証が絡むレスポンスの注意点

認証が絡むレスポンスでは、個人情報やユーザー固有の内容がキャッシュされないように注意する必要があります。ここを間違えると、情報漏えいにつながる可能性があります。

ログイン中のユーザー情報、購入履歴、設定画面などは、ユーザーごとに内容が違います。こうしたレスポンスを共有キャッシュに保存してしまうと、別ユーザーへ返ってはいけない内容が再利用される危険があります。そのため、認証が絡むAPIでは privateno-store を検討することが多いです。

Cache-Control: no-store

この設定は、レスポンスを保存しないように指示します。注意点は、認証レスポンスでもすべて同じとは限らないことです。たとえば公開プロフィールとマイページでは要件が違うため、「認証があるから全部同じ設定」ではなく、含まれる情報の性質で判断することが大切です。

5. キャッシュでよくある落とし穴

5-1. 更新したファイルが反映されない

キャッシュでよくある落とし穴は、更新したCSSやJSがブラウザに反映されないことです。これは古いファイルがキャッシュとして有効なまま使われている可能性があります。

特に、同じURLのままファイル内容だけを差し替える運用では、ブラウザが「まだ使える」と判断して古いキャッシュを使い続けることがあります。CDNを使っている場合は、ブラウザだけでなくCDN側にも古い内容が残っているかもしれません。つまり、更新反映の問題はサーバーだけでなく、複数のキャッシュ層を見る必要があります。

対策としては、ビルド成果物にハッシュ付きファイル名を使う、CDNのキャッシュ削除手順を用意する、HTMLと静的アセットでキャッシュ方針を分ける、などがあります。キャッシュを完全に無効化するより、更新時に正しく新しいURLへ切り替える設計のほうが実務では扱いやすいです。

5-2. 個人情報を含むレスポンスをキャッシュしてしまう

もうひとつの大きな落とし穴は、個人情報を含むレスポンスを不適切にキャッシュしてしまうことです。これはパフォーマンス問題ではなく、セキュリティ問題になります。

たとえば、ユーザー名、メールアドレス、住所、購入履歴、管理画面の情報などは、他人に見えてはいけません。こうしたレスポンスを共有キャッシュに保存してしまうと、条件次第で別ユーザーへ返る危険があります。キャッシュは便利ですが、「誰に見せても同じ内容か」を確認せずに設定すると危険です。

個人情報を含むレスポンスでは、Cache-Control: no-storeprivate を検討します。特にCDNやプロキシがある構成では、レスポンスがどこに保存される可能性があるかを意識する必要があります。

5-3. no-cacheを「キャッシュしない」と誤解する

no-cache は、キャッシュを保存しないという意味ではありません。この名前のせいで誤解されやすいですが、実際には「使う前に再検証して」という意味です。

no-cache のレスポンスは、保存されることがあります。ただし、再利用する前にサーバーへ確認し、変更がない場合だけ使います。本当に保存させたくない場合は、no-store を使う必要があります。つまり、no-cacheno-store は明確に使い分けるべきです。

たとえば、最新性を確認したうえで再利用したいリソースなら no-cache が合うことがあります。一方、機密情報を含むレスポンスは保存自体を避けたいので no-store が候補です。名前だけで判断せず、実際の動作で理解することが大切です。

6. まとめ

6-1. Cache-ControlとETagの役割整理

Cache-ControlETag は、どちらもHTTPキャッシュに関係しますが、役割は違います。同じものとして覚えると、設定の意図が分からなくなります。

Cache-Control は、保存してよいか、何秒使ってよいか、共有キャッシュへ保存してよいか、といったキャッシュ方針を指定します。一方、ETag は、保存済みの内容がサーバー側と同じかを確認するための識別子です。304 Not Modified は、再検証の結果「変わっていないので手元のキャッシュを使ってよい」と伝えるレスポンスです。

静的ファイルでは長めのキャッシュとファイル名ハッシュを組み合わせやすく、APIでは内容や認証の有無に応じて慎重に決める必要があります。キャッシュは悪者ではなく、正しく使えば速度と通信量を改善できる重要な仕組みです。

6-2. キャッシュ設定チェックリスト

HTTPキャッシュを設計するときは、レスポンスの性質に合わせて設定を選ぶことが大切です。すべてのレスポンスに同じ設定を付けると、古い表示や情報漏えいの原因になります。

  • このレスポンスは誰が見ても同じ内容か
  • どれくらい古い情報まで許容できるか
  • ブラウザだけでなくCDNにも保存してよいか
  • 個人情報や認証状態を含んでいないか
  • 静的ファイルはハッシュ付きファイル名になっているか
  • no-cacheno-store を使い分けているか
  • ETagやLast-Modifiedで再検証できる設計になっているか
  • DevToolsでキャッシュ状態と304を確認できるか

キャッシュで詰まったときは、「保存期間」「再検証」「保存場所」「個人情報の有無」を順番に見ると切り分けしやすいです。速くするための設定が、古い表示や漏えいにつながらないように、リソースごとの方針を決めておきましょう。

7. 参考リンク