Cache-Controlヘッダーの基礎
HTTPキャッシュを制御する Cache-Control ヘッダーの仕組みとディレクティブの意味、よく使うパターンを理解する
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 だけではキャッシュの「有効期限」しか伝えられない。期限切れ後の「更新されたかどうかの確認(再検証)」には ETag と Last-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-Match | ETag による条件付き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=6060秒間はキャッシュを使ってよい。CDNでもキャッシュ可。
パターン④:コンテンツハッシュ付き静的ファイル(JS/CSS)
Cache-Control: public, max-age=31536000, immutableファイル名にハッシュが含まれているため、URLが変わらない限りコンテンツも変わらない。1年間キャッシュを維持してよい。
パターン⑤:HTMLファイル
Cache-Control: no-cacheHTMLはJSやCSSへの参照を含むため、常に最新版を使いたい。ただし no-store にすると毎回フルで転送するため、no-cache(保存するが毎回再検証)が多く使われる。
よくある誤解
no-cache ≠ キャッシュしない
繰り返しになるが、no-cache は「キャッシュを使う前に再検証せよ」という意味。キャッシュを完全に防ぎたいなら no-store を使う。
max-age=0 は no-store と同じではない
max-age=0 は「取得直後から期限切れ」という意味で、次回アクセス時に再検証は発生するが、レスポンス自体はキャッシュに保存される。no-store はそもそも保存しない。
POST は自動的にキャッシュされない、は半分正しい
デフォルトでは POST レスポンスはキャッシュされない。ただし RFC7234 上では Cache-Control ヘッダーが適切に付いていれば POST レスポンスもキャッシュできる。「POST だから安全」ではなく、ヘッダーで明示的に制御することが重要。