#はじめに
「no-cacheを設定したのにキャッシュされている」——これは最もよくある誤解の一つです。Cache-Controlのディレクティブは、名前だけでは動作がわかりにくいものがあります。
この記事では、各ディレクティブの正確な意味と使い分けを学びます。
この記事を読むと、以下のことができるようになります:
- 各Cache-Controlディレクティブの正確な意味を理解できる
- 用途に応じて適切なディレクティブを選択できる
- よくある誤設定を避けられる
#有効期限の指定
#max-age
レスポンスがfreshである期間を秒単位で指定します。
Cache-Control: max-age=3600
この例では、3600秒(1時間)の間、サーバーへの確認なしにキャッシュを使用できます。
取得時刻: 10:00
有効期限: 11:00(10:00 + 3600秒)
#s-maxage
共有キャッシュ(CDN等)専用の有効期限を指定します。
Cache-Control: max-age=60, s-maxage=3600
- ブラウザ: 60秒間キャッシュ
- CDN: 3600秒間キャッシュ
CDNでは長くキャッシュしつつ、ブラウザでは頻繁に更新を確認させたい場合に使用します。
#max-stale(リクエスト用)
stale(期限切れ)のキャッシュを使用してもよい期間を指定します。
Cache-Control: max-stale=300
オフライン時や低速ネットワーク時に、古いキャッシュでも表示したい場合に使用します。
#min-fresh(リクエスト用)
指定秒数以上freshであるキャッシュのみを使用します。
Cache-Control: min-fresh=600
「あと10分以上有効なキャッシュでなければ使わない」という指定です。
#キャッシュ可否の制御
#no-store
キャッシュを一切禁止します。
Cache-Control: no-store
機密情報を含むレスポンスに使用します。
- 銀行口座の残高
- 個人情報
- 一時的なトークン
注意: これでも、ブラウザの「戻る」ボタンで表示される可能性はあります。
#no-cache
キャッシュしてもよいが、使用前に必ずサーバーで検証する必要があります。
Cache-Control: no-cache
よくある誤解: 「no-cache = キャッシュしない」ではありません。
# no-cache の動作
1. レスポンスをキャッシュに保存
2. 次回使用時、サーバーに「まだ有効?」と確認
3. サーバーが「有効」と回答 → キャッシュを使用
4. サーバーが「無効」と回答 → 新しいレスポンスを取得
常に最新かどうかを確認したいが、変更がなければネットワーク転送を省きたい場合に使用します。
#no-cacheとno-storeの違い
| ディレクティブ | キャッシュ保存 | 使用時の検証 |
|---|---|---|
no-store | ❌ しない | - |
no-cache | ✅ する | 必ず検証 |
max-age=0 | ✅ する | 検証必要 |
機密情報にはno-store、常に最新性を確認したい情報にはno-cacheを使用します。
#キャッシュ場所の制御
#public
共有キャッシュ(CDN、プロキシ)でもキャッシュ可能であることを明示します。
Cache-Control: public, max-age=3600
通常、max-ageがあれば暗黙的にpublicとして扱われますが、認証が必要なリクエストに対するレスポンスを共有キャッシュに保存したい場合に明示します。
#private
ブラウザキャッシュのみ許可し、共有キャッシュは禁止します。
Cache-Control: private, max-age=3600
ユーザー固有の情報を含むレスポンスに使用します。
# 例: ユーザーのダッシュボード
Cache-Control: private, max-age=300
# CDNはキャッシュしない(他のユーザーに表示されてしまう)
# ブラウザはキャッシュする(同じユーザーの再アクセスを高速化)
#再検証の制御
#must-revalidate
staleになったキャッシュは、必ずサーバーで検証してから使用する必要があります。
Cache-Control: max-age=3600, must-revalidate
通常、staleなキャッシュは「できれば検証する」程度ですが、must-revalidateがあると「必ず検証する」になります。サーバーに到達できない場合は、キャッシュを使用せずエラーを返します。
#proxy-revalidate
共有キャッシュ(CDN等)のみに適用されるmust-revalidateです。
Cache-Control: max-age=3600, proxy-revalidate
ブラウザではstaleなキャッシュを使用してもよいが、CDNでは必ず再検証させたい場合に使用します。
#stale-while-revalidate
staleなキャッシュを返しつつ、バックグラウンドで再検証します。
Cache-Control: max-age=60, stale-while-revalidate=300
この例では:
- 60秒間: freshなキャッシュを使用
- 60-360秒: staleなキャッシュを返しつつ、バックグラウンドで再検証
- 360秒以降: 再検証してから返す
ユーザーには即座にレスポンスを返しつつ、次回のために最新化できます。
#stale-if-error
サーバーエラー時に、staleなキャッシュを使用してもよい期間を指定します。
Cache-Control: max-age=60, stale-if-error=86400
サーバーがダウンしても、24時間は古いキャッシュで代替できます。
#immutable
リソースが決して変更されないことを示します。
Cache-Control: max-age=31536000, immutable
通常、ページをリロードするとブラウザはキャッシュの再検証を行います。immutableがあると、有効期限内はリロードでも再検証をスキップします。
主にファイル名にハッシュを含むリソースに使用します。
# ファイル名にハッシュが含まれる
/assets/app.a1b2c3d4.js
# 内容が変われば名前も変わるので、再検証は不要
Cache-Control: max-age=31536000, immutable
#典型的な設定パターン
#静的アセット(ハッシュ付きファイル名)
Cache-Control: max-age=31536000, immutable
1年間キャッシュ。ファイル名にハッシュを含めることで、更新時は新しいURLに。
#HTML(動的コンテンツ)
Cache-Control: no-cache
常に最新性を確認。変更なければ304で高速応答。
#API レスポンス(ユーザー固有)
Cache-Control: private, no-cache
ブラウザでのみキャッシュ可、使用時は検証必須。
#機密情報
Cache-Control: no-store
キャッシュ禁止。
#CDN経由の画像
Cache-Control: public, max-age=86400, s-maxage=604800
ブラウザで1日、CDNで1週間キャッシュ。
#複数ディレクティブの組み合わせ
複数のディレクティブはカンマで区切ります。
Cache-Control: public, max-age=3600, must-revalidate
順序は意味を持ちませんが、慣例として以下の順序がよく使われます:
public/private/no-store/no-cachemax-age/s-maxagemust-revalidateなど
#まとめ
- no-store: キャッシュを一切禁止
- no-cache: キャッシュするが使用前に必ず検証
- max-age: キャッシュの有効期間(秒)
- s-maxage: 共有キャッシュ用の有効期間
- public/private: キャッシュ場所の制御
- must-revalidate: stale時に必ず検証
- immutable: 変更されないリソースに使用
- stale-while-revalidate: stale提供+バックグラウンド更新
#次のステップ
Cache-Controlディレクティブを理解したところで、次は条件付きリクエストについて学びましょう。ETagとLast-Modifiedを使って、「変更されていなければデータを送らない」という効率的なキャッシュ検証の仕組みを理解します。