awesome-hacks
Docs

Cache-Controlヘッダーの基礎

HTTPキャッシュを制御する Cache-Control ヘッダーの仕組みとディレクティブの意味、よく使うパターンを理解する

最終更新:2026/06/16

TL;DR

  • Cache-Control はHTTPレスポンスやリクエストに付けるヘッダーで、「このリソースをどこに・どれくらい・どのようにキャッシュしてよいか」をブラウザやプロキシに伝える
  • 機密情報を含むAPIには no-store を付ける。no-cache は「キャッシュするが毎回再検証する」で意味が異なるので注意
  • 静的ファイル(JS/CSS)には max-age を長く設定し、URLを変えることで更新を通知する(キャッシュバスティング)
  • private / public はプロキシやCDNへのキャッシュ許可を制御する

Cache-Control とは

Cache-Control は HTTP/1.1 で定義されたヘッダーで、レスポンスをキャッシュする主体(ブラウザ・プロキシ・CDN)に対して「このレスポンスをどう扱うべきか」を指示する。

HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json

キャッシュが発生する場所は複数ある。

ブラウザ ─── ブラウザキャッシュ(ローカル)
   ↓
プロキシ / CDN ─── 共有キャッシュ(複数ユーザーで共有)
   ↓
オリジンサーバー

Cache-Control はこれらの各レイヤーに向けてキャッシュルールを伝える仕組みである。


レスポンスの Cache-Control ディレクティブ

no-store

キャッシュを一切行わない。 レスポンスの内容をどこにも保存しない。

Cache-Control: no-store
  • ブラウザもプロキシも保存しない
  • 毎回サーバーへリクエストが飛ぶ
  • 個人情報・認証情報・機密データを含むAPIに使う

no-cache

キャッシュするが、使う前に必ずサーバーへ再検証する。

Cache-Control: no-cache

名前に反して「キャッシュしない」という意味ではない。**「キャッシュに保存してよいが、使用前にサーバーに問い合わせて有効かを確認せよ」**という意味。

サーバーが 304 Not Modified を返せばキャッシュを使用し、200 OK を返せば新しいレスポンスを使う。毎回サーバーへの確認は必要だが、コンテンツ転送量を減らせるケースがある。

max-age=N

N秒間はサーバーへ問い合わせずにキャッシュを使用してよい。

Cache-Control: max-age=3600   // 1時間
Cache-Control: max-age=86400  // 1日
Cache-Control: max-age=31536000  // 約1年

max-age=0 は「取得直後から古い」とみなされ、再検証が必要になる(no-cache と実質同等)。

s-maxage=N

共有キャッシュ(CDN・プロキシ)専用の max-age ブラウザの max-age を上書きする。

Cache-Control: max-age=600, s-maxage=86400

この例では「ブラウザには10分キャッシュ、CDNには1日キャッシュ」という指示になる。

private

ブラウザ(個人キャッシュ)のみキャッシュを許可し、共有キャッシュへの保存を禁止する。

Cache-Control: private, max-age=300

ログインユーザー向けのページや、ユーザーごとに内容が変わるAPIレスポンスに使う。CDNにキャッシュされると他ユーザーに見えてしまう。

public

共有キャッシュ(CDN・プロキシ)にもキャッシュを許可する。

Cache-Control: public, max-age=31536000

認証不要の静的リソース(画像・JS・CSS)に使う。

must-revalidate

max-age が切れたら必ずサーバーに再検証し、古いキャッシュを使わない。

Cache-Control: max-age=3600, must-revalidate

通常、max-age が切れたコンテンツは条件によっては再利用されることがある(stale-while-revalidateなど)。must-revalidate はそれを禁止し、期限切れなら必ずサーバーへ問い合わせることを保証する。

immutable

コンテンツは変わらない。 max-age の有効期間中、再検証リクエストを送らない。

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

コンテンツハッシュをファイル名に含めた静的ファイルに使う(app.a1b2c3.js など)。ファイル名が変わればURLが変わるので、URLが同じなら中身も同じという保証がある。


ディレクティブのまとめ

ディレクティブ対象意味
no-storeブラウザ・共有一切保存しない
no-cacheブラウザ・共有保存するが毎回再検証する
max-age=NブラウザN秒間有効
s-maxage=N共有キャッシュのみ共有キャッシュ用のN秒
privateブラウザのみ共有キャッシュに保存禁止
public共有キャッシュ共有キャッシュに保存許可
must-revalidateブラウザ期限切れなら必ず再検証
immutableブラウザ期限内は再検証不要

ETag・Last-Modified との関係

Cache-Control だけではキャッシュの「有効期限」しか伝えられない。期限切れ後の「更新されたかどうかの確認(再検証)」には ETagLast-Modified が使われる。

条件付きリクエストの流れ

1回目のリクエスト:
  GET /api/data
  ← 200 OK
  ← Cache-Control: max-age=60
  ← ETag: "abc123"          ← サーバーがコンテンツの識別子を返す

60秒後(max-ageが切れたら):
  GET /api/data
  If-None-Match: "abc123"   ← 「このETagと同じ?」
  ← 304 Not Modified        ← 変わってなければ本文なしで返す
  (キャッシュをそのまま使う)

コンテンツが変わっていれば:
  ← 200 OK + 新しいコンテンツ + 新しいETag
ヘッダー役割
ETagコンテンツの識別子(ハッシュ値など)
Last-Modified最終更新日時
If-None-MatchETag による条件付きGET
If-Modified-Since日時による条件付きGET

よく使うパターン

パターン①:機密情報を含むAPIレスポンス

Cache-Control: no-store

ログインユーザーの個人情報、認証トークン、財務データなど。絶対にキャッシュさせない。

パターン②:ユーザーごとに内容が異なるAPI

Cache-Control: private, no-cache

内容はユーザー固有(private)だが、最新性を保ちたいので再検証する(no-cache)。

パターン③:認証不要の一般的なAPIデータ

Cache-Control: public, max-age=60

60秒間はキャッシュを使ってよい。CDNでもキャッシュ可。

パターン④:コンテンツハッシュ付き静的ファイル(JS/CSS)

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

ファイル名にハッシュが含まれているため、URLが変わらない限りコンテンツも変わらない。1年間キャッシュを維持してよい。

パターン⑤:HTMLファイル

Cache-Control: no-cache

HTMLはJSやCSSへの参照を含むため、常に最新版を使いたい。ただし no-store にすると毎回フルで転送するため、no-cache(保存するが毎回再検証)が多く使われる。


よくある誤解

no-cache ≠ キャッシュしない

繰り返しになるが、no-cache は「キャッシュを使う前に再検証せよ」という意味。キャッシュを完全に防ぎたいなら no-store を使う。

max-age=0no-store と同じではない

max-age=0 は「取得直後から期限切れ」という意味で、次回アクセス時に再検証は発生するが、レスポンス自体はキャッシュに保存される。no-store はそもそも保存しない。

POST は自動的にキャッシュされない、は半分正しい

デフォルトでは POST レスポンスはキャッシュされない。ただし RFC7234 上では Cache-Control ヘッダーが適切に付いていれば POST レスポンスもキャッシュできる。「POST だから安全」ではなく、ヘッダーで明示的に制御することが重要。


関連