HTTPキャッシュ完全ガイド:追加インフラなしでWebサイトを高速化する
Cache-Controlヘッダー、ETag、CDNキャッシュ戦略、ブラウザキャッシュの動作をマスターして、サイトの読み込み時間を劇的に改善し、サーバーコストを削減しましょう。
キャッシュは、実施できるパフォーマンス最適化の中で最も費用対効果が高いものです。キャッシュされたレスポンスはマイクロ秒単位で返され、コストはゼロで、サーバー処理も一切不要です。それにもかかわらず、多くのアプリケーションはキャッシュを過剰に使用して古いコンテンツを返すか、まったく使用せずに帯域幅とコンピューティングリソースを無駄にしています。HTTPキャッシュを理解すれば、バグの原因から強力な武器へと変えることができます。
HTTPキャッシュの仕組み
ブラウザやCDNがレスポンスを受信すると、ヘッダーを確認してキャッシュするかどうか、またどのくらいの期間キャッシュするかを決定します。同じリソースへの次のリクエストでは、サーバーに一切アクセスせずにキャッシュされたコピーを返すことができます。
キャッシュのライフサイクルには2つのフェーズがあります:
- 鮮度(Freshness) — キャッシュされたコピーはまだ有効か?
Cache-Control: max-ageまたはExpiresによって決まります。 - 検証(Validation) — 期限切れの場合、コンテンツが変更されていないことをサーバーに確認できるか?
ETagまたはLast-Modifiedによって決まります。
Cache-Control:主要なキャッシュディレクティブ
Cache-Control は最も強力なキャッシュヘッダーです。カンマ区切りのディレクティブのリストで構成されています:
Cache-Control: public, max-age=31536000, immutable
主要なディレクティブ
| ディレクティブ | 意味 |
|---|---|
public |
任意のキャッシュ(ブラウザ、CDN、プロキシ)が保存可能 |
private |
エンドユーザーのブラウザのみキャッシュ可能(CDN不可) |
no-cache |
使用前に毎回サーバーで再検証が必要(「キャッシュしない」ではない) |
no-store |
一切キャッシュしない — 機密データ向け |
max-age=N |
N秒間キャッシュする |
s-maxage=N |
CDN専用の最大有効期間(共有キャッシュではmax-ageを上書き) |
immutable |
コンテンツは変更されない — 再検証を完全にスキップ |
must-revalidate |
期限切れ時は、返す前に再検証が必要 |
stale-while-revalidate=N |
バックグラウンドで新しいコンテンツを取得しながら、N秒間古いキャッシュを返す |
⚠️
no-cacheは「キャッシュしない」という意味ではありません。「キャッシュするが、常に有効性を確認する」という意味です。本当にキャッシュさせたくない場合はno-storeを使用してください。
リソースタイプ別のキャッシュ戦略
リソースの種類によって、適切な戦略が異なります:
コンテンツハッシュを使用した静的アセット(CSS、JS、画像)
Cache-Control: public, max-age=31536000, immutable
ビルドツールがファイル名にハッシュを付加する場合(main.a3f9b2c.js)、コンテンツが変更されるとURLも変わります。新しいバージョンには新しいURLが付与されるため、永続的にキャッシュできます。
HTMLページ
Cache-Control: no-cache
または短いTTLを設定:
Cache-Control: public, max-age=60, stale-while-revalidate=3600
HTMLは頻繁に変更され、ハッシュ付きアセットへのリンクを含みます。短時間のキャッシュか、強制的な再検証を行いましょう。
APIレスポンス
# 公開データ(商品カタログなど)
Cache-Control: public, max-age=300, stale-while-revalidate=600
# ユーザー固有のデータ
Cache-Control: private, max-age=60
# リアルタイムデータ(株価、ライブスコアなど)
Cache-Control: no-store
機密データ(認証、決済)
Cache-Control: no-store
絶対にキャッシュしないでください。
ETagと条件付きリクエスト
キャッシュされたレスポンスの有効期限が切れると、ブラウザはそれを破棄するのではなく、サーバーに対してまだ有効かどうかを確認します。これが再検証です。
ETag
ETagはレスポンスコンテンツのフィンガープリントです:
# サーバーが送信:
ETag: "a3f9b2c8d4e1"
# ブラウザの次のリクエスト:
If-None-Match: "a3f9b2c8d4e1"
# 変更がなければ、サーバーは以下を返す:
HTTP/1.1 304 Not Modified
(ボディなし — 帯域幅を節約)
# 変更があれば、サーバーは以下を返す:
HTTP/1.1 200 OK
ETag: "b7c2d4e9a1f3"
(完全な新しいレスポンス)
304 Not Modified レスポンスにはボディがなく、ヘッダーのみです。これにより、コンテンツを再ダウンロードする際の帯域幅をすべて節約できます。
Last-Modified
ハッシュの代わりにタイムスタンプを使用する類似の仕組みです:
Last-Modified: Tue, 01 Apr 2026 10:00:00 GMT
# ブラウザが送信:
If-Modified-Since: Tue, 01 Apr 2026 10:00:00 GMT
ETagの方が信頼性が高いです(ロードバランサーを使用した環境ではタイムスタンプが不正確になることがあります)。
Varyヘッダー:リクエストのバリエーションごとにキャッシュ
Vary ヘッダーは、どのリクエストヘッダーがレスポンスに影響するかをキャッシュに伝えます:
Vary: Accept-Encoding
これにより、gzip と br のレスポンスに対して別々のコピーがキャッシュされます。よく使われる例:
Vary: Accept-Encoding # 圧縮/非圧縮で別々のキャッシュ
Vary: Accept-Language # 言語ごとに別々のキャッシュ
Vary: Accept # JSONとHTMLレスポンスで別々のキャッシュ
⚠️
Vary: CookieまたはVary: Authorizationを使用すると、CDNキャッシュが実質的に無効になります — CDNはユーザー固有のレスポンスをキャッシュできません。
stale-while-revalidate:バックグラウンド更新
最も便利なモダンキャッシュパターンの一つです:
Cache-Control: max-age=60, stale-while-revalidate=600
- 最初の60秒間はキャッシュから即座に返す(鮮度あり)
- 60〜660秒の間のリクエスト:古いキャッシュを即座に返しつつ、バックグラウンドで新しいバージョンを取得
- 660秒以降:返す前に再検証が必要
ユーザーは常に高速なレスポンスを受け取れます。誰もネットワークのラウンドトリップを待つことなく、キャッシュは新鮮な状態を保てます。
CDNキャッシュに関する考慮事項
CDN(Cloudflare、CloudFront、Fastly)は Cache-Control ヘッダーを尊重しますが、独自の複雑さも加わります:
-
s-maxageを使用すると、CDNとブラウザで異なるTTLを設定できます:Cache-Control: public, max-age=60, s-maxage=86400ブラウザは1分間キャッシュし、CDNは24時間キャッシュします。
-
キャッシュのパージ — デプロイ時には、更新されたアセットのCDNキャッシュをパージしてください。ほとんどのCDNはAPIベースのパージ機能を提供しています。
-
キャッシュキー — CDNはURL + Varyヘッダーに基づいてキャッシュします。クエリ文字列は通常キャッシュキーに含まれます。
キャッシュ動作のテスト
API Request Builder を使用してレスポンスヘッダーを確認し、キャッシュ設定が正しく機能しているかを検証しましょう:
- リクエストを送信し、
Cache-Control、ETag、Last-Modifiedヘッダーを確認する - 同じリクエストを再度送信 —
Ageヘッダー(キャッシュからの経過秒数)とX-Cache: HITを確認する CF-Cache-Status(Cloudflare)またはX-Cache(CloudFront)を確認してCDNキャッシュを検証する
Chrome DevTools → Networkタブ → リソースをクリック → Headersタブで、レスポンス内の from disk cache または from memory cache を確認してください。
Nginxキャッシュ設定
一貫した動作を実現するために、Nginxレベルでキャッシュヘッダーを設定します:
# 静的アセット — 永続的にキャッシュ
location ~* \.(js|css|woff2|png|jpg|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML — 常に再検証
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API — stale-while-revalidateを使った短期キャッシュ
location /api/ {
add_header Cache-Control "public, max-age=60, stale-while-revalidate=600";
}
Nginx Config Generator を使用して、用途に合わせた完全で最適化されたNginx設定を生成できます。
キャッシュチェックリスト
- 静的アセット(CSS/JS)にコンテンツハッシュのファイル名 +
max-age=31536000, immutableを使用している - HTMLに
no-cacheまたは非常に短いmax-ageを設定している - APIレスポンスを更新頻度に基づいてキャッシュしている
- 機密データに
no-storeを使用している - 条件付きリクエストのためにETagまたは
Last-Modifiedを有効にしている - 適切なAPIエンドポイントに
stale-while-revalidateを設定している - CDNキャッシュヘッダーを検証・テスト済みである
適切なHTTPキャッシュを実装することで、リピーターにとってサイトが瞬時に表示され、帯域幅コストが削減され、サーバー負荷が軽減されます — 追加インフラなしで、これらすべてを実現できます。