Developer Tools

HTTPキャッシュ完全ガイド:追加インフラなしでWebサイトを高速化する

Cache-Controlヘッダー、ETag、CDNキャッシュ戦略、ブラウザキャッシュの動作をマスターして、サイトの読み込み時間を劇的に改善し、サーバーコストを削減しましょう。

7分で読めます

データセンターのサーバーラック

キャッシュは、実施できるパフォーマンス最適化の中で最も費用対効果が高いものです。キャッシュされたレスポンスはマイクロ秒単位で返され、コストはゼロで、サーバー処理も一切不要です。それにもかかわらず、多くのアプリケーションはキャッシュを過剰に使用して古いコンテンツを返すか、まったく使用せずに帯域幅とコンピューティングリソースを無駄にしています。HTTPキャッシュを理解すれば、バグの原因から強力な武器へと変えることができます。

HTTPキャッシュの仕組み

ブラウザやCDNがレスポンスを受信すると、ヘッダーを確認してキャッシュするかどうか、またどのくらいの期間キャッシュするかを決定します。同じリソースへの次のリクエストでは、サーバーに一切アクセスせずにキャッシュされたコピーを返すことができます。

キャッシュのライフサイクルには2つのフェーズがあります:

  1. 鮮度(Freshness) — キャッシュされたコピーはまだ有効か?Cache-Control: max-age または Expires によって決まります。
  2. 検証(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

これにより、gzipbr のレスポンスに対して別々のコピーがキャッシュされます。よく使われる例:

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 を使用してレスポンスヘッダーを確認し、キャッシュ設定が正しく機能しているかを検証しましょう:

  1. リクエストを送信し、Cache-ControlETagLast-Modified ヘッダーを確認する
  2. 同じリクエストを再度送信 — Age ヘッダー(キャッシュからの経過秒数)と X-Cache: HIT を確認する
  3. 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キャッシュを実装することで、リピーターにとってサイトが瞬時に表示され、帯域幅コストが削減され、サーバー負荷が軽減されます — 追加インフラなしで、これらすべてを実現できます。