#はじめに

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

順序は意味を持ちませんが、慣例として以下の順序がよく使われます:

  1. public / private / no-store / no-cache
  2. max-age / s-maxage
  3. must-revalidate など

#まとめ

  • no-store: キャッシュを一切禁止
  • no-cache: キャッシュするが使用前に必ず検証
  • max-age: キャッシュの有効期間(秒)
  • s-maxage: 共有キャッシュ用の有効期間
  • public/private: キャッシュ場所の制御
  • must-revalidate: stale時に必ず検証
  • immutable: 変更されないリソースに使用
  • stale-while-revalidate: stale提供+バックグラウンド更新

#次のステップ

Cache-Controlディレクティブを理解したところで、次は条件付きリクエストについて学びましょう。ETagとLast-Modifiedを使って、「変更されていなければデータを送らない」という効率的なキャッシュ検証の仕組みを理解します。

#参考リンク