はじめに
「ページをリロードしたら一瞬で表示されるけど、初回は遅かった。なぜ?」
その答えは キャッシュ です。Webの世界では、同じデータを何度もダウンロードしなくて済むように、さまざまなレイヤーでキャッシュが活用されています。
しかし、キャッシュを正しく設定しないと 「更新したのに古い表示のまま」 という厄介な問題が起こります。この記事では、Cache-ControlやETagの仕組みから実践的な設定パターンまでを図解で解説します。
この記事は 「図解でわかるWeb技術の仕組み」シリーズ の第8回です。
第7回:WebSocket ― 双方向リアルタイム通信を先に読むと、よりスムーズに理解できます。
1. キャッシュとは ― なぜ必要なのか
一言でいうと
キャッシュ とは、一度取得したデータのコピーを近い場所に保存しておき、次回以降のアクセスを高速化する仕組みです。
キャッシュがないとどうなるか
ブラウザでページを開くたびに、すべてのリソース(HTML、CSS、JS、画像)をサーバーから再ダウンロードします。
| 問題 | 影響 |
|---|---|
| 表示速度の低下 | ユーザー体験が悪化 |
| サーバー負荷の増大 | 同じデータを何度も生成・転送 |
| 帯域の浪費 | モバイル回線ではデータ量がコストに直結 |
2. Webキャッシュの3つのレイヤー
Webのキャッシュは 3つの層 で構成されています。リクエストがオリジンサーバーに届く前に、各レイヤーでキャッシュが確認されます。

各レイヤーの特徴
| レイヤー | 保存場所 | 対象ユーザー | 主な用途 |
|---|---|---|---|
| ブラウザキャッシュ | ユーザーの端末 | そのユーザーのみ | 再訪時の高速表示 |
| CDNキャッシュ | エッジサーバー | 全ユーザー | 静的ファイルの高速配信 |
| サーバーキャッシュ | アプリサーバー/Redis | 全リクエスト | DB負荷の軽減 |
リクエストの流れ:ブラウザキャッシュ → CDNキャッシュ → サーバーキャッシュ → DB
キャッシュがヒットすれば、その時点でレスポンスを返せるため、後段への問い合わせが不要になります。
3. Cache-Control ― キャッシュの司令塔
Cache-Control は、HTTPレスポンスヘッダーでキャッシュの動作を制御する 最も重要なディレクティブ です。
主要ディレクティブの詳細
public と private
Cache-Control: public, max-age=3600
Cache-Control: private, max-age=3600
-
public: CDN・プロキシを含め、どこでもキャッシュしてよい -
private: ブラウザだけがキャッシュしてよい(CDNには保存されない)
ユーザー固有のデータ(マイページ、カート情報など)には必ず private を指定してください。 public にすると、CDN上に他ユーザーのデータがキャッシュされ、情報漏洩につながるリスクがあります。
max-age
Cache-Control: max-age=86400
キャッシュの 有効期間(秒数) を指定します。この期間中はサーバーへの問い合わせなしでキャッシュを使用します。
| 設定例 | 期間 | 用途 |
|---|---|---|
max-age=0 |
0秒(毎回検証) | 動的コンテンツ |
max-age=3600 |
1時間 | 頻繁に更新されるAPI |
max-age=86400 |
1日 | 画像ファイル |
max-age=31536000 |
1年 | ハッシュ付きJS/CSS |
no-cache と no-store の違い
この2つは 名前が紛らわしい ですが、動作は全く異なります。
| ディレクティブ | キャッシュ保存 | サーバー検証 | 用途 |
|---|---|---|---|
no-cache |
する | 毎回必要 | HTMLページ、APIレスポンス |
no-store |
しない | - | 機密データ(パスワード、口座情報) |
no-cache は「キャッシュするな」ではありません。 正しくは「キャッシュしてよいが、使う前に必ずサーバーに確認しろ」という意味です。「キャッシュするな」は no-store です。
4. ETag / Last-Modified ― 条件付きリクエスト
no-cache でサーバーに確認するとき、毎回フルでデータを返していたら効率が悪いです。そこで登場するのが 条件付きリクエスト です。
仕組み
-
初回アクセス: サーバーがレスポンスに
ETagやLast-Modifiedを付与 -
2回目以降: ブラウザが
If-None-Match(ETag用)やIf-Modified-Since(Last-Modified用)を送信 - 変更なし: サーバーは 304 Not Modified(ボディなし)を返す → ブラウザはキャッシュを使う
- 変更あり: サーバーは 200 OK + 新しいデータを返す
ETag vs Last-Modified
# ETag(コンテンツのハッシュ値)
ETag: "abc123def456"
# Last-Modified(最終更新日時)
Last-Modified: Mon, 01 Jan 2026 00:00:00 GMT
| 比較項目 | ETag | Last-Modified |
|---|---|---|
| 精度 | バイト単位で変更を検出 | 秒単位 |
| 計算コスト | ハッシュ計算が必要 | ファイルシステムから取得(軽量) |
| 推奨度 | 高(より正確) | 中(フォールバック用) |
ETagとLast-Modifiedは併用できます。 両方あればETagが優先されます。Last-ModifiedはETag非対応のクライアントへのフォールバックとして有用です。
5. キャッシュ戦略の判断フローチャート
「このリソースにはどのCache-Controlを設定すべきか?」を判断するフローチャートです。
判断のポイント
-
機密データか? →
no-storeで一切キャッシュしない -
CDNに載せてよいか? → ユーザー固有なら
private -
コンテンツは変わるか? → 不変なら長期キャッシュ、変わるなら
no-cacheで都度検証
6. リソース別キャッシュ設定ガイド
実務でよく使うリソースごとの推奨設定をまとめます。
Nginxでの設定例
# 静的アセット(ハッシュ付きファイル名)
location ~* \.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 画像
location ~* \.(png|jpg|gif|svg|webp)$ {
add_header Cache-Control "public, max-age=86400";
}
# HTMLページ
location ~* \.html$ {
add_header Cache-Control "no-cache";
etag on;
}
# API
location /api/ {
add_header Cache-Control "private, max-age=0, must-revalidate";
etag on;
}
キャッシュバスティング
「長期キャッシュを設定したのに、ファイルを更新したい」場合は キャッシュバスティング を使います。
<!-- ファイル名にハッシュを含める(推奨) -->
<script src="/js/app.a1b2c3d4.js"></script>
<link rel="stylesheet" href="/css/style.e5f6g7h8.css">
<!-- ビルドツール(Vite, webpack等)が自動でハッシュを付与 -->
ファイルの内容が変われば ハッシュが変わる → URLが変わる → ブラウザは新しいファイルとして取得 します。これにより、max-age=31536000(1年)のキャッシュを安全に設定できます。
7. よくある落とし穴
① CDNにユーザー固有データがキャッシュされる
# 危険: ユーザー固有のAPIレスポンスにpublicを設定
Cache-Control: public, max-age=3600
# 安全: privateを指定
Cache-Control: private, max-age=0, must-revalidate
② no-cache と no-store の混同
# 「キャッシュして検証」(HTMLページ向け)
Cache-Control: no-cache
# 「一切キャッシュしない」(機密データ向け)
Cache-Control: no-store
③ Service Workerのキャッシュを忘れる
ブラウザキャッシュとは別に、Service Worker が独自にキャッシュを持つことがあります。Cache-Controlだけでなく、Service Workerのキャッシュ戦略も合わせて設計してください。
8. まとめ
| 項目 | ポイント |
|---|---|
| キャッシュの目的 | 同じデータの再取得を避け、表示速度とサーバー負荷を改善 |
| 3つのレイヤー | ブラウザキャッシュ → CDNキャッシュ → サーバーキャッシュ |
| Cache-Control | キャッシュの動作を制御する最重要ヘッダー |
| public / private | CDNにキャッシュさせるか、ブラウザ限定か |
| no-cache / no-store | 毎回検証 / 一切保存しない(名前に注意) |
| ETag / Last-Modified | 条件付きリクエストで304応答を活用し、転送量を削減 |
| キャッシュバスティング | ハッシュ付きファイル名で長期キャッシュと更新を両立 |
次回予告
第9回では 「CORS ― クロスオリジンの壁を理解する」 を図解で解説します。「なぜフロントエンドからAPIを叩くとエラーになるのか?」というWeb開発最大の罠を攻略します。
シリーズ目次
| # | テーマ |
|---|---|
| 1 | HTTPリクエスト/レスポンスの仕組み |
| 2 | Cookie・Session・JWTの違い |
| 3 | OAuth 2.0 の仕組み |
| 4 | DNS解決の流れ |
| 5 | HTTPS・TLS通信の仕組み |
| 6 | CDN の仕組みと役割 |
| 7 | WebSocket ― 双方向リアルタイム通信 |
| 8 | キャッシュ戦略(ブラウザ・サーバー・CDN)(この記事) |
| 9 | CORS ― クロスオリジンの壁を理解する |
| 10 | REST API 設計の基本原則 |
「この記事が役に立った」と思ったら、LGTM とストックをお願いします。
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!



