Cache-Control - ブラウザ・CDNキャッシュの制御

入門 | 9分 で読める | 2026.05.02

公式ドキュメント

Cache-Controlとは

Cache-Controlは、HTTPレスポンスやリクエストに付与され、キャッシュの保存・再利用・検証ルールを細かく制御するヘッダーです。適切に設定することで、サーバー負荷を削減し、ページ表示速度を大幅に改善できます。

なぜ必要か: 同じリソースを毎回サーバーから取得すると、通信コストと待ち時間が無駄になります。キャッシュを活用することで、ユーザー体験が向上し、インフラコストも削減できます。

キャッシュの基本フロー

1. 初回リクエスト

ブラウザがリソースを初めて取得します。

GET /style.css HTTP/1.1
Host: example.com

レスポンス:

HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000
Content-Length: 1234

2. キャッシュからの取得

有効期限内なら、ブラウザはサーバーに問い合わせず、キャッシュから直接取得します(ネットワークタブに「(disk cache)」と表示)。

3. 期限切れ後の再検証

max-age期限が切れると、ブラウザはサーバーに検証リクエストを送ります。

GET /style.css HTTP/1.1
If-None-Match: "abc123"

内容が変わっていない場合:

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=31536000

ポイント: 304 Not Modifiedレスポンスはボディを含まないため、転送量が大幅に削減されます。ETagやLast-Modifiedと組み合わせて使います。

Cache-Controlディレクティブ一覧

Cache-Controlのno-cacheは保存後の再利用前に検証し、no-storeはHTTP Cacheへ保存しない違いを示す図

キャッシュ可否の制御

ディレクティブ説明用途
public共有キャッシュにも保存できることを明示認証付きでも共有してよい公開リソース
private共有キャッシュへの保存を禁止ユーザー固有のデータ
no-cache保存はできるが、再利用前に検証が必要常に最新か確認したいリソース
no-storeこのレスポンスをキャッシュへ保存しない機密性の高いレスポンス

有効期限の制御

ディレクティブ説明
max-age=Nブラウザ用の有効期限(秒)max-age=3600 (1時間)
s-maxage=NCDN/共有キャッシュ用の有効期限s-maxage=86400 (24時間)
immutablefreshな期間は更新確認が不要だと示すmax-age=31536000, immutable

再検証の制御

ディレクティブ説明用途
must-revalidatestaleになった後、通常は検証なしで再利用しない鮮度を厳密に扱うデータ
proxy-revalidateCDN向けのmust-revalidateCDNだけ厳密にしたい場合
stale-while-revalidate=N期限切れ後N秒は古いキャッシュを返しつつ裏で更新高速応答と鮮度の両立
stale-if-error=Nエラー時は期限切れキャッシュをN秒まで利用可用性重視

実践メモ: immutableは、内容が変わればURLも変わるハッシュ付きファイル(例: `app.a1b2c3.js`)に向いています。freshな期間の不要な再検証を減らせます。

よく使うパターン

静的アセット(JS/CSS/画像)

ハッシュ付きファイル名の場合:

Cache-Control: public, max-age=31536000, immutable
  • 理由: ファイル名が変わらない限り内容も不変。1年間キャッシュOK。
  • 例: app.a1b2c3.js, logo.7f8e9d.png

ハッシュなしファイル名の場合:

Cache-Control: public, max-age=3600
  • 理由: 1時間程度のキャッシュで、更新にも対応。
  • 例: style.css, main.js

HTML

Cache-Control: public, max-age=0, must-revalidate

または

Cache-Control: no-cache
  • 理由: HTMLは常に最新を確認。ただし304で高速応答。
  • 例: index.html, about.html

API レスポンス

公開データ:

Cache-Control: public, max-age=60, s-maxage=300
  • ブラウザ: 1分キャッシュ
  • CDN: 5分キャッシュ

ユーザー固有データ:

Cache-Control: private, max-age=0, no-cache
  • 理由: CDNにキャッシュさせず、ブラウザも毎回検証。

機密性の高いレスポンス:

Cache-Control: no-store
  • 理由: このレスポンスをHTTPキャッシュへ保存させない。

CDN配信(静的サイト)

Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800
  • ブラウザ: 1時間
  • CDN: 24時間
  • 古いキャッシュも1週間は裏で更新しつつ即応答

注意: no-cacheは「キャッシュしない」ではなく「毎回検証する」という意味です。「キャッシュしない」はno-storeを使います。

no-cache vs no-store の違い

no-cache

Cache-Control: no-cache

動作:

  1. キャッシュに保存する
  2. 使用前に必ずサーバーに検証
  3. 304 Not Modifiedなら再利用

用途: 常に最新を確認したいが、変更がなければキャッシュを活用したい場合。

no-store

Cache-Control: no-store

動作:

  1. このレスポンスをキャッシュに保存しない
  2. 毎回サーバーから全データを取得
  3. 304レスポンスも使わない

用途: 機密情報、個人情報、銀行の残高など。

no-storeは、すでに保存済みのレスポンスを削除する命令ではなく、ブラウザ履歴などHTTPキャッシュ以外の保存まで消去する保証もありません。漏えい対策は、認可・ログ・画面表示なども含めて設計します。

実装例

Express.js

const express = require('express');
const app = express();

// 静的ファイル(ハッシュ付き)
app.use('/assets', express.static('public/assets', {
 maxAge: '1y',
 immutable: true
}));

// 静的ファイル(ハッシュなし)
app.use('/images', express.static('public/images', {
 maxAge: '1h'
}));

// HTML
app.get('/', (req, res) => {
 res.set('Cache-Control', 'public, max-age=0, must-revalidate');
 res.sendFile('index.html');
});

// API(公開データ)
app.get('/api/posts', (req, res) => {
 res.set('Cache-Control', 'public, max-age=60, s-maxage=300');
 res.json({ posts: [...] });
});

// API(ユーザー固有)
app.get('/api/me', (req, res) => {
 res.set('Cache-Control', 'private, no-cache');
 res.json({ user: {...} });
});

// 機密情報
app.get('/api/payment', (req, res) => {
 res.set('Cache-Control', 'no-store');
 res.json({ card: {...} });
});

Nginx

# ハッシュ付き静的ファイル
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff|woff2)$ {
 expires 1y;
 add_header Cache-Control "public, immutable";
}

# HTML
location ~* \.html$ {
 add_header Cache-Control "public, max-age=0, must-revalidate";
}

# API(公開)
location /api/public/ {
 add_header Cache-Control "public, max-age=60, s-maxage=300";
 proxy_pass http://backend;
}

# API(ユーザー固有)
location /api/private/ {
 add_header Cache-Control "private, no-cache";
 proxy_pass http://backend;
}

Cloudflare Workers

export default {
 async fetch(request) {
 const url = new URL(request.url);
 
 // 静的アセット
 if (url.pathname.startsWith('/assets/')) {
 const response = await fetch(request);
 const headers = new Headers(response.headers);
 headers.set('Cache-Control', 'public, max-age=31536000, immutable');
 return new Response(response.body, { ...response, headers });
 }
 
 // HTML
 if (url.pathname.endsWith('.html')) {
 const response = await fetch(request);
 const headers = new Headers(response.headers);
 headers.set('Cache-Control', 'public, max-age=0, must-revalidate');
 return new Response(response.body, { ...response, headers });
 }
 
 return fetch(request);
 }
};

キャッシュ判定フロー

以下のフローチャートでブラウザのキャッシュ判定を理解できます。

リクエスト受信
 ↓
キャッシュあり?
 ↓ YES
no-store?
 ↓ NO
max-age期限内?
 ↓ YES
キャッシュから返す(200 from cache)
 ↓ NO
no-cache または must-revalidate?
 ↓ YES
サーバーに検証(If-None-Match / If-Modified-Since)
 ↓
304 Not Modified?
 ↓ YES
キャッシュから返す(304)
 ↓ NO
新しいレスポンスを返す(200)

stale-while-revalidate の活用

最新のディレクティブで、パフォーマンスと鮮度を両立します。

Cache-Control: max-age=60, stale-while-revalidate=86400

動作:

  1. 60秒以内: キャッシュから即座に返す
  2. 60秒〜24時間: 古いキャッシュを即座に返しつつ、裏でサーバーに更新をリクエスト
  3. 24時間以降: サーバーに問い合わせてから返す

メリット:

  • ユーザーは常に高速応答を得られる
  • サーバーは適度な頻度で更新を検知できる

ポイント: stale-while-revalidateはCDNでの活用が効果的。エッジで古いキャッシュを返しつつ、オリジンへの問い合わせを非同期化できます。

max-age と s-maxage の使い分け

ディレクティブ対象典型的な値用途
max-ageブラウザキャッシュ短め(数分〜1時間)ユーザーが頻繁に更新を確認
s-maxageCDN/共有キャッシュ長め(数時間〜1日)CDNで長くキャッシュして負荷削減

例:

Cache-Control: public, max-age=300, s-maxage=3600
  • ブラウザ: 5分キャッシュ
  • CDN: 1時間キャッシュ

シナリオ: ニュースサイトの記事一覧。ユーザーは5分ごとに新着をチェックしたいが、CDNは1時間キャッシュして負荷を削減。

デバッグとトラブルシューティング

Chrome DevTools で確認

  1. Network タブを開く
  2. リソースをクリック
  3. Headers タブで Cache-Control を確認
  4. Size 列で (disk cache)(memory cache) を確認

キャッシュクリアの方法

# ハードリロード(キャッシュ無視)
Ctrl + Shift + R (Win/Linux)
Cmd + Shift + R (Mac)

# キャッシュとCookie削除
Chrome DevTools → Application → Clear Storage

古いキャッシュを削除したい場合

Cache-Controlは、すでに保存されたレスポンスを一括削除する命令ではありません。確実に更新を反映するには、ハッシュ付きURLへ切り替える、CDNのパージ機能を使う、利用者側でブラウザキャッシュを消す、といった対象別の対応が必要です。Pragma: no-cacheはHTTP/1.0互換の古いリクエスト用ヘッダーで、レスポンスの削除命令にはなりません。

ベストプラクティス

1. リソース種別ごとに最適化

リソースCache-Control理由
HTMLno-cache常に最新を確認
JS/CSS (hash)max-age=31536000, immutableURLが変わるまで長期再利用
JS/CSS (no hash)max-age=3600適度に更新
画像max-age=864001日キャッシュ
APIprivate, max-age=60ユーザー別・短時間

2. ハッシュ付きファイル名を採用

# Webpack/Vite等で自動生成
app.a1b2c3.js
style.d4e5f6.css

ファイル名が変わるため、古いキャッシュの心配なし。

3. Vary ヘッダーと組み合わせ

Cache-Control: public, max-age=3600
Vary: Accept-Encoding, Accept-Language

Accept-Encodingや言語ごとに異なるキャッシュを保持。

注意: Expires ヘッダーよりCache-Controlが優先されます。両方あるとCache-Controlが勝ちます

まとめ

Cache-Controlは、レスポンスの保存先、再利用できる期間、再検証の要否を決めるヘッダーです。まずハッシュ付き静的ファイル、更新されるHTML、ユーザー固有APIを分け、その性質に合わせてpublic/privatemax-age/s-maxageno-cache/no-storeを選びます。immutablestale-while-revalidateは、更新方法や許容できる古さが明確な場合に使います。

参考リソース

← 一覧に戻る
PR
PR
PR
PR