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

入門 | 12分 で読める | 2026.05.02

公式ドキュメント

この記事の要点

Cache-ControlはブラウザとCDNのキャッシュ動作を制御するHTTPヘッダー
max-ageでキャッシュ有効期限を秒単位で指定
no-cacheは「キャッシュしない」ではなく「毎回検証する」の意味

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ディレクティブ一覧

キャッシュ可否の制御

ディレクティブ説明用途
publicCDN含め誰でもキャッシュ可能静的な公開リソース
privateブラウザのみキャッシュ可能(CDN不可)ユーザー固有のデータ
no-cacheキャッシュするが毎回検証必須常に最新を確認したいリソース
no-store一切キャッシュしない機密情報、個人情報

有効期限の制御

ディレクティブ説明
max-age=Nブラウザ用の有効期限(秒)max-age=3600 (1時間)
s-maxage=NCDN/共有キャッシュ用の有効期限s-maxage=86400 (24時間)
immutable期限内は絶対に変更されないmax-age=31536000, immutable

再検証の制御

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

実践メモ: immutableはハッシュ付きファイル名(例: `app.a1b2c3.js`)専用です。リロード時の不要な検証を完全に防げます。

よく使うパターン

静的アセット(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
  • 理由: キャッシュを一切残さない。

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レスポンスも使わない

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

実装例

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: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

PragmaExpiresは古いブラウザ対応です。

ベストプラクティス

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

リソースCache-Control理由
HTMLno-cache常に最新を確認
JS/CSS (hash)max-age=31536000, immutable永久キャッシュ
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は、Webパフォーマンス最適化の最重要ヘッダーです。リソースの性質に応じてpublic/privatemax-age/s-maxageno-cache/no-storeを使い分けることで、高速なユーザー体験とサーバー負荷削減を両立できます。特にimmutablestale-while-revalidateは、モダンなWebサイトで積極的に活用すべきディレクティブです。

参考リソース

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

メールで無料相談する
← 一覧に戻る