配信とキャッシュ設計:Cache-Control / ETag / SWR の最短ガイド(Next.js)
速さと整合性はCache-Control の設計で決まります。静的は immutable
、動的は s-maxage
と stale-while-revalidate
、ユーザー固有は no-store
。 Next.js の headers()
/ fetch()
/ revalidate
を組み合わせれば、CDN配信で速いのに壊れない運用ができます。
先に結論(運用ルール)
- 画像・フォント等の静的アセット:
public, max-age=31536000, immutable
- HTML/APIの動的キャッシュ:
max-age=0, s-maxage=3600, stale-while-revalidate=86400
- ユーザー固有/機微:
private, no-store
(クッキー依存・個人化)
要点(TL;DR)
- 静的は1年+immutableでブラウザ&CDNに“永続”キャッシュ。
- HTML/APIはCDN優先(
s-maxage
)+SWRで新鮮度と速さを両立。 - ユーザー固有/機微はno-storeでキャッシュ禁止。
fetch(..., { next: { revalidate: N } })
で再検証周期をコードから制御。
1. なぜキャッシュ設計が効くか
速度指標(LCP/INP/TTFB)は配信レイヤの影響が大きいです。immutable
でアセットを確実にCDN/ブラウザへ定着させ、 HTML は s-maxage + SWR
で“早く返しつつ裏で更新”。破壊的変更はファイル名のコンテンツハッシュで担保します。
2. 最短フロー(実務)
- アセットを immutable:
public/
配下や画像/フォントにmax-age=31536000, immutable
を付与。 - HTML/API は CDN 優先:
max-age=0, s-maxage=3600, stale-while-revalidate=86400
を付与。 - データ再検証:
fetch
のnext.revalidate
を設定(例:60秒)。 - 個人化は no-store:クッキー/セッション依存のレスポンスはキャッシュ禁止。
3. 実装レシピ(コピペOK)
3.1 next.config.js の headers()
JavaScript
// next.config.js(ESM)
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
// 1) public/ の画像・フォントなど“ほぼ不変”の静的アセット
{
source: "/:path*\\.(avif|webp|png|jpg|jpeg|gif|svg|ico|mp4|webm|woff2)",
headers: [
{ key: "Cache-Control", value: "public, max-age=31536000, immutable" },
],
},
// 2) API(ユーザー固有レスポンスなど)は都度生成
{
source: "/api/:path*",
headers: [{ key: "Cache-Control", value: "private, no-store" }],
},
// 3) HTML は CDN 優先(オリジンは常に最新) + SWR
{
source: "/articles/:slug",
headers: [
{ key: "Cache-Control", value: "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400" },
],
},
];
},
};
export default nextConfig;
3.2 fetch の revalidate 設計
TypeScript
// サーバーコンポーネント/Route Handler 内のデータ取得
// ISR相当:最長60秒で自動再検証(CDNはs-maxageに従う)
const res = await fetch("https://example.com/data.json", {
next: { revalidate: 60 }, // 0で常新, 60で最大1分再利用
// cache: "force-cache" | "no-store" | "reload"(基本は指定不要でOK)
});
const data = await res.json();
3.3 Route Handler のキャッシュ制御
TypeScript
// app/api/news/route.ts(サンプル)
// CDNは1時間キャッシュ、バックグラウンドで1日までSWR
export const revalidate = 3600; // fetch()の既定再検証秒
export async function GET() {
const res = await fetch("https://example.com/news.json", { next: { revalidate } });
const body = await res.text();
return new Response(body, {
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400",
// ETagは Response が自動付与する環境もあります(CDN/プラットフォーム依存)
},
});
}
4. 応用と使いどころ
- OGP/サムネのCDN配信:生成後は
immutable
。テンプレは OGPサムネ完全設計参照。 - ZIP配布の最適化:アセット一式は
immutable
+ 配布UXで回遊性を確保。 - 画像の遅延読み込みとINP:INP時代の遅延読み込み と組み合わせて“速く・崩れない”を維持。
5. 公開前チェック
- 画像/フォント/動画:
immutable
で1年キャッシュになっている。 - HTML/API:
s-maxage
とstale-while-revalidate
が適切。 - 個人化レスポンス:
no-store
になっている。 - 破壊的変更時:ファイル名のコンテンツハッシュが変わる。
6. まとめ
静的は immutable
、動的は s-maxage + SWR
、個人化は no-store
。 Next.js の仕組みを活かせば、配信はシンプルに回せます。画像そのものの品質は /compressor で最適化して、配信と合わせて総合最適にしましょう。
7. FAQ
immutable と ETag はどちらを使う?
どちらも併用できます。
immutable
は“そもそも取りに行かない”指示、ETag
は取りに行って差分のみ返す仕組みです。 変更時はファイル名をハッシュ化してURLを変えるのが鉄則です。max-age と s-maxage の違いは?
max-age
はブラウザ、s-maxage
はCDN(共有キャッシュ)向けです。HTMLは max-age=0, s-maxage=…
とすることで、ブラウザは再取得しつつCDNは高速に返せます。FAQ(よくある質問)
画像形式の基本方針は?(写真/スクショ/透過)
写真は AVIF / WebP(画質80–85%目安)、UIやスクショはPNG / WebP Lossless、単色ロゴはSVGが基本です。 実装の詳細は srcset/sizes設計ガイド と スクショ最適化 を参照してください。
圧縮しても画質を落とさないコツは?
実表示幅に合わせたリサイズ → 過大ダウンロードを防ぎ、
srcset/sizes
を 実描画幅に一致させます。画質は写真で 80–85% を起点に、ノイズやエッジを目視確認。 仕上げは /compare で Before/After を見比べるのがおすすめです。