画像CDNとキャッシュ制御の最適解 — Cache-Control/SWR/immutable/ETag
画像の体験はキャッシュ戦略で決まります。更新されないアセットは“強いキャッシュ”で固定し、 頻繁に変わる画像はSWR(stale-while-revalidate)で整合性と体感を両立。Vary: Accept
の濫用や CDNキー設計のミスは、ヒット率低下や遅延の温床です。本稿は実務に直結する“最短解”をテンプレとともに示します。
先に結論(このルールで始める)
- ハッシュ付き静的画像:
public, max-age=31536000, immutable
- 差し替え頻度が高い画像:
public, max-age=300, stale-while-revalidate=86400
- フォーマットは拡張子分岐+srcset/sizesで配信。
Vary: Accept
は最小限。 - 差し替えはURLバージョン(指紋)で行い、CDNパージは例外対応に限定。
要点(TL;DR)
- ハッシュ付き静的はimmutable、可変はSWR。
Vary: Accept
を濫用しない。拡張子分岐+srcsetで単純に。- ETag/Last-Modifiedは未知の更新頻度に有効だが、往復は発生。
- CDNのキャッシュキーとオリジンのヘッダー上書きに注意。
1. 戦略マトリクス(資産×ヘッダー)
資産例 | 推奨ヘッダー | 理由 |
---|---|---|
/assets/logo.abcd12.png | public, max-age=31536000, immutable | URLが内容ハッシュなので安全に長期保存 |
/user/123/avatar.webp | public, max-age=300, stale-while-revalidate=86400 | 即返しつつ裏で更新。体験と整合性の両立 |
/thumbs/article-*.webp | public, max-age=86400 | 日次で変わる想定。ほどよい寿命 |
2. 結論テンプレ(そのまま使える)
# ハッシュ付き静的画像(/assets/logo.abcd12.png) Cache-Control: public, max-age=31536000, immutable # 差し替え頻度が高い画像(/user/123/avatar.webp) Cache-Control: public, max-age=300, stale-while-revalidate=86400 # 受容ヘッダー出し分けをする場合のみ(CDNのキー設計が必須) Vary: Accept
3. Vary: Accept を多用しない理由
- CDNのキャッシュキーが増え、ヒット率が低下。オリジン到達が増えて遅延・コスト増。
- 代替は拡張子分岐(
.avif/.webp/.jpg
)+ HTML の srcset/sizes。 単純で運用ミスが少ない。
4. 設定例(サーバ/エッジ)
A. Next.js Route Handler
// app/images/[file]/route.ts import { NextResponse } from "next/server"; export async function GET(_: Request, { params }: { params: { file: string } }) { const file = params.file; const res = await fetch("https://origin.example.com/images/" + file); const headers = new Headers(res.headers); headers.set("Cache-Control", "public, max-age=300, stale-while-revalidate=86400"); return new NextResponse(res.body, { headers, status: res.status }); }
B. Nginx(静的配信)
location ~* \.(avif|webp|jpg|jpeg|png|gif|svg)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
5. ETag/If-None-Match の使いどころ
更新頻度が読めない場合は ETag で差分転送。ただしヘッダー往復は発生するため、 回線が細い地域では“強いキャッシュ”のほうが体験が良い場面も多いです。迷ったら「指紋URL+immutable」を第一選択に。
6. 検証と監視(チェック手順)
- ヘッダー確認:
curl -I https://example.com/assets/logo.abcd12.png
- CDNのヒット率(CF-Cache-Status / X-Cache 等)を監視。
- 差し替えはURLバージョンで実施(パージ乱用はしない)。
7. 運用の落とし穴
- CDNでのヘッダー上書きにより、オリジンの意図が無効化される。
- URLハッシュ運用なのにpurgeを多発して二重運用になっている。
- Varyやクエリパラメータがキーを分断し、ヒット率を落としている。
8. まとめ:immutable×SWRの二刀流で堅く速く
画像キャッシュは「変わらないものはimmutable」「変わるものはSWR」が基本。srcset/sizes と組み合わせて 過大ダウンロードを避け、優先度設計でLCPにも効かせましょう。