HTTP キャッシュ完全ガイド:強力キャッシュ・検証キャッシュ・CDN 戦略を徹底解説
Web/SEO伊藤 結衣
HTTP キャッシュ:パフォーマンス最適化の第一防衛線
キャッシュを適切に設定すれば、2 回目以降のアクセスでネットワークリクエストがゼロになり、読み込み時間は秒単位からミリ秒単位へ短縮されます。
| キャッシュ種別 | 仕組み | サーバーへのリクエスト | 速度 |
|---|---|---|---|
| 強力キャッシュ | Cache-Control / Expires | なし | 最速(0ms) |
| 検証キャッシュ | ETag / Last-Modified | あり(条件付きリクエスト) | 速い(304 レスポンス) |
| キャッシュなし | no-cache / no-store | あり(完全リクエスト) | 最も遅い |
一、強力キャッシュ
Cache-Control ディレクティブ
Cache-Control: max-age=31536000, immutable
| ディレクティブ | 効果 | 適用シーン |
|---|---|---|
max-age=N |
N 秒間キャッシュ | 静的リソース |
s-maxage=N |
CDN が N 秒間キャッシュ(max-age を上書き) | CDN 環境 |
immutable |
期限切れ後も再検証しない | ハッシュ付きファイル名 |
public |
中間キャッシュを許可 | CDN |
private |
ブラウザのみキャッシュ | ユーザー個別のコンテンツ |
no-cache |
使用前に必ず検証 | HTML ページ |
no-store |
一切キャッシュしない | 機密データ |
must-revalidate |
期限切れ後は必ず検証 | API レスポンス |
stale-while-revalidate=N |
期限切れ後は古い値を返しつつ裏で更新 | 段階的な更新 |
Expires(旧仕様)
Expires: Thu, 02 Jun 2027 00:00:00 GMT
優先順位:Cache-Control: max-age > Expires。
二、検証キャッシュ
ETag / If-None-Match
# 初回リクエスト
GET /api/data
→ 200 OK
ETag: "abc123"
Cache-Control: max-age=0
# 再リクエスト(条件付き)
GET /api/data
If-None-Match: "abc123"
→ 304 Not Modified (レスポンスボディなし、帯域を節約)
Last-Modified / If-Modified-Since
# 初回リクエスト
GET /style.css
→ 200 OK
Last-Modified: Mon, 01 Jun 2026 12:00:00 GMT
# 再リクエスト
GET /style.css
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT
→ 304 Not Modified
ETag vs Last-Modified
| 観点 | ETag | Last-Modified |
|---|---|---|
| 精度 | バイト単位 | 秒単位 |
| パフォーマンス | ハッシュ計算が必要 | ファイル時刻を読むだけ |
| 信頼性 | 高い | 低い(1 秒以内の複数変更を検知できない) |
| 分散環境 | ハッシュの一貫性が必要 | 本質的に一貫 |
推奨:ETag を優先する。
三、キャッシュ戦略マトリクス
HTML ページ
Cache-Control: no-cache
HTML はエントリーファイルであり、ユーザーが常に最新版を取得できるよう毎回検証が必要です。
ハッシュ付き静的リソース
Cache-Control: max-age=31536000, immutable
ファイル名にハッシュを含む場合(例:app.abc123.js)、内容が変わるとハッシュが変わり → URL が変わり → 新しいリクエストが発生します。古い URL は永久に変わらないため、1 年間キャッシュして immutable を付けられます。
ハッシュなし静的リソース
Cache-Control: max-age=86400, stale-while-revalidate=604800
期限切れ後は古い値を先に返し、裏で静かに更新するため、ユーザーは何も感じません。
API レスポンス
Cache-Control: private, max-age=0, must-revalidate
API レスポンスには通常ユーザーデータが含まれます。private で CDN のキャッシュを禁止し、must-revalidate で期限切れ後に古い値を返さないようにします。
四、Vary ヘッダー:キャッシュキーの拡張
Vary: Accept-Encoding, Accept-Language
Vary はキャッシュに対し「URL 以外にも、これらのリクエストヘッダーの値でキャッシュを区別せよ」と伝えます。
| Vary の値 | 意味 | シーン |
|---|---|---|
Accept-Encoding |
圧縮方式ごとにキャッシュ | gzip vs br |
Accept-Language |
言語ごとにキャッシュ | 多言語サイト |
Cookie |
Cookie ごとにキャッシュ | パーソナライズページ |
* |
リクエストごとに独立してキャッシュ | キャッシュしないのと同等 |
注意:Vary: Cookie はキャッシュヒット率を極端に下げるため、慎重に使いましょう。
五、CDN キャッシュ戦略
オリジン取得の制御
# CDN は 1 日、ブラウザは 5 分キャッシュ
Cache-Control: public, s-maxage=86400, max-age=300
| シーン | s-maxage | max-age | 説明 |
|---|---|---|---|
| 静的リソース | 31536000 | 31536000 | CDN・ブラウザともに長期キャッシュ |
| API レスポンス | 300 | 0 | CDN は 5 分キャッシュ、ブラウザはキャッシュしない |
| HTML | 60 | 0 | CDN は 1 分キャッシュ、ブラウザはキャッシュしない |
| 個別コンテンツ | — | 0 | CDN・ブラウザともにキャッシュしない |
CDN キャッシュのパージ
| 方法 | 速度 | 適用 |
|---|---|---|
| URL プリウォーム | 速い | 新コンテンツの公開 |
| 全量パージ | 遅い | 緊急修正 |
| ディレクトリパージ | 中程度 | 一括更新 |
| Cache-Control: no-cache | 自動 | API シーン |
Stale-While-Revalidate 実践
Cache-Control: max-age=60, stale-while-revalidate=86400
0 秒目:リクエスト → キャッシュ MISS → オリジン取得 → 新鮮な値を返す
30 秒目:リクエスト → キャッシュ HIT → キャッシュ値を返す
90 秒目:リクエスト → キャッシュ STALE → 古い値を即座に返す + 裏でオリジン取得
91 秒目:裏のオリジン取得が完了 → キャッシュ更新
120 秒目:リクエスト → キャッシュ HIT → 新しい値を返す
効果:ユーザーはオリジン取得を一切待たず、裏で静かに更新されます。
六、キャッシュバスティング戦略
コンテンツハッシュ(推奨)
<!-- ビルド時にハッシュを生成 -->
<script src="/app.abc123.js"></script>
<link href="/style.def456.css">
// webpack 設定
output: {
filename: '[name].[contenthash:8].js',
}
バージョンクエリパラメータ
<!-- 簡単だが、CDN が ?v= パラメータをキャッシュしない場合がある -->
<script src="/app.js?v=2.0.0"></script>
問題点:一部の CDN はクエリパラメータを無視し、キャッシュが更新されないことがあります。
タイムスタンプ(非推奨)
<!-- 毎回変わるためキャッシュが完全に無効化される -->
<script src="/app.js?t=1717315200"></script>
七、Service Worker キャッシュ
Cache First 戦略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
Stale While Revalidate 戦略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic').then(cache =>
cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request)
.then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
})
)
);
});
戦略の選び方
| 戦略 | 優先ソース | 適用 |
|---|---|---|
| Cache First | キャッシュ | フォント、画像 |
| Network First | ネットワーク | API、HTML |
| Stale While Revalidate | キャッシュ+裏で更新 | CSS、JS |
| Network Only | ネットワーク | GET 以外のリクエスト |
| Cache Only | キャッシュ | オフラインページ |
八、キャッシュのデバッグ
Chrome DevTools
- DevTools → Network パネル
Size列を確認:from disk cache:ディスクキャッシュにヒットfrom memory cache:メモリキャッシュにヒット- 実際のサイズ:キャッシュミス
Ageレスポンスヘッダーを確認:キャッシュが存在してからの経過時間- Disable cache にチェック:キャッシュを無効化(DevTools 表示中のみ)
curl で検証
# レスポンスヘッダーを確認
curl -I https://example.com/style.css
# 条件付きリクエストを送信
curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
# 304 は検証キャッシュのヒットを示す
# 異なる Accept-Encoding をシミュレート
curl -I -H 'Accept-Encoding: br' https://example.com/style.css
九、ベストプラクティス チェックリスト
- HTML は no-cache:エントリーファイルを常に最新に
- ハッシュ付き JS/CSS は max-age=1 年 + immutable
- API は must-revalidate:期限切れ後は必ず検証
- CDN は s-maxage でオリジン取得の頻度を制御
- Vary: Accept-Encoding で圧縮方式を区別
- Last-Modified ではなく ETag を使う
- stale-while-revalidate でシームレスな更新を実現
- Service Worker でオフライン機能を補完
#HTTP缓存#Cache-Control#ETag#CDN#性能优化