Lompat ke konten utama
友田 陽大
Vercel in production
Vercel
Next.js
パフォーマンス
ISR
キャッシュ
コスト最適化
フロントエンド

Vercel キャッシュ戦略ガイド:ISR・CDN Cache・Runtime Cache・Cache Components(PPR) の4層を使い分ける

Vercel公式に忠実なキャッシュ実装ガイド。静的キャッシュ・ISR(stale-while-revalidate/31日永続/300msグローバルpurge/リクエスト併合)・CDN Cache(Cache-Control/CDN-Cache-Control/Vercel-CDN-Cache-Controlの3ヘッダ)・Runtime Cache・Cache Components(PPR)の4層を、on-demand revalidation(revalidatePath/revalidateTag/updateTag)とx-vercel-cacheの読み方まで実コードで解説します。

Published
Reading time
10 min read
Author
友田 陽大
Bagikan

「ページが遅い」の解決策は1つではありません。Vercel は4つのキャッシュ層を提供しており、「レスポンス全体か/個別データ片か」「フレームワーク連携か/手動制御か」で使い分けます。層を取り違えると、キャッシュされていないのにされていると思い込む(そして Active CPU 課金とレイテンシで損をする)か、逆に古いデータを出し続ける事故になります。

この記事は ISRCDN CacheRuntime Cache の公式仕様に忠実に、4層を実コードで整理します。Next.js の Cache Components / PPR そのものの深掘りは Next.js 16 App Router の Cache Components 記事 に譲り、本稿は 「Vercel プラットフォームとしてどうキャッシュが効くか」 に集中します。全体像は Vercel 本番運用ガイド を参照してください。


4層の地図:まずどれを使うか

対象設定永続性代表的な用途
静的ファイルビルド出力(JS/CSS/画像/フォント)自動デプロイ寿命(ハッシュ名で跨ぎ持続)全デプロイ。考えなくてよい
ISRページ(HTML+データ)フレームワークAPI(revalidate等)31日・再検証までスケジュール更新のページ(EC・CMS・生成系)
CDN Cache関数のHTTPレスポンスCache-Control 系ヘッダ最大1年(ベストエフォート)ISR非対応・APIレスポンスを地域別にキャッシュ
Runtime Cache関数のデータ片Runtime Cache APIタグ無効化までfetch結果・DBクエリ・計算値の再利用

判断のショートカット:

  • ページ全体を出すフレームワーク(Next.js等)→ ISR(最強の機能群が自動で付く)
  • API のレスポンスを地域キャッシュ → CDN Cache(Cache-Control)
  • 関数の中で同じデータを何度も取るRuntime Cache
  • 静的アセット → 何もしない(自動

ISR:最も多機能なキャッシュ

stale-while-revalidate の挙動

ISR は「キャッシュ済みを即返し、裏で再生成」。Vercel の CDN に乗ることで、フレームワークの ISR は次を自動で得ます(ISR docs)。

  • 31日間の永続ストレージ:Function リージョンに永続。デプロイごとに独立したキャッシュ。
  • 300ms のグローバル purge:再検証時、全リージョンが 300ms 以内に更新。HTML とデータをアトミックに差し替える(全ページ遷移とクライアント遷移で内容が食い違わない)。
  • リクエスト併合:未キャッシュの同一パスに同時アクセスが殺到しても、リージョンごとに関数を1回だけ呼ぶ。スパイク時のオリジン保護。
  • 即時ロールバック耐性:過去デプロイのキャッシュは消えないので、ロールバックしても生成済みコンテンツを失わない。
  • キャッシュシールド:CDN ミス時、関数を呼ぶ前に永続 ISR キャッシュを読む。
// app/blog/[slug]/page.tsx
// 時間ベース再検証:3600秒ごとに裏で再生成
export const revalidate = 3600;

// 人気ページはビルド時に事前生成、それ以外はオンデマンド生成
export async function generateStaticParams() {
  const popular = await getPopularSlugs();
  return popular.map((slug) => ({ slug }));
}

export default async function Post({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return <Article post={await getPost(slug)} />;
}

オンデマンド再検証:イベントで即時反映

「3600秒待たず、CMS で公開した瞬間に反映したい」——これがオンデマンド再検証です。revalidatePathrevalidateTag を使います。

// app/api/revalidate/route.ts — CMS の Webhook から叩く
import { revalidatePath, revalidateTag } from "next/cache";

export async function POST(request: Request) {
  // ① 認可(無防備な再検証エンドポイントは攻撃面になる)
  if (request.headers.get("authorization") !== `Bearer ${process.env.REVALIDATE_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }

  const { slug, tag } = await request.json();

  if (slug) revalidatePath(`/blog/${slug}`); // パス単位
  if (tag) revalidateTag(tag);               // タグ単位(複数ページ横断)

  return Response.json({ revalidated: true });
}

データ取得側でタグを付けておくと、revalidateTag("posts") のように横断的に無効化できます。

// fetch にタグを付ける(Next.js)
const posts = await fetch("https://cms.example.com/posts", {
  next: { tags: ["posts"], revalidate: 3600 },
}).then((r) => r.json());

再検証が失敗したら? Vercel はステイルを返し続け、30秒の TTL で再試行します。再検証成功とみなすステータスは 200/301/302/307/308/404/410 のみ。これ以外・ネットワークエラー・関数エラーは失敗扱いです。だから「再検証先が一時的に落ちても、ユーザーには古いページが出続ける」——可用性に効く設計です。

対応フレームワーク

ISR は Next.js 専用ではありません。

フレームワーク有効化
Next.js(App Router)ルートセグメントから revalidate を export
Next.js(Pages Router)getStaticPropsrevalidate を返す
SvelteKitconfigisr プロパティ
NuxtrouteRulesisr
Astroserver 出力で ISR 設定
GatsbyDSG(Deferred Static Generation)

Cache Components(PPR):静的シェルと動的穴の両立

Next.js 16 の Cache Components(Partial Prerendering, PPR) は、「1ページの中で、静的な殻(シェル)を即配信しつつ、動的な部分だけ穴を空けてストリームする」モデルです。use cache ディレクティブで境界を宣言し、cacheLifecacheTag で寿命とタグを制御します。

// app/dashboard/page.tsx
import { Suspense } from "react";

export default function Dashboard() {
  return (
    <>
      <StaticHeader />               {/* 静的シェル:即配信 */}
      <Suspense fallback={<Skel />}>
        <UserStats />               {/* 動的な穴:ストリーム */}
      </Suspense>
    </>
  );
}
// キャッシュ境界を宣言(コンポーネント/関数単位)
async function getCatalog() {
  "use cache";
  cacheLife("hours");     // 寿命プロファイル
  cacheTag("catalog");    // タグ付け → updateTag/revalidateTag で無効化
  return db.products.findMany();
}

Vercel 上では、この静的シェルが ISR/CDN に乗り、動的穴は Fluid Compute の関数がストリーミングで埋めます。タグ単位の無効化updateTag/revalidateTag)で、書き込み後に該当タグだけ即時更新できます。

Cache Components の API(use cachecacheLifecacheTagupdateTagunstable_cache からの移行)の詳細は、フロントエンドクラスタの Next.js 16 Cache Components 記事 に集約しています。本稿では「Vercel 上でどう配信されるか」だけ押さえれば十分です。


CDN Cache:関数レスポンスを3ヘッダで制御

ISR を使わない API・SSR でも、Cache-Control を返せばそのリージョンの CDN にキャッシュされます。Vercel はブラウザ/下流CDN/Vercel CDN を別々に制御できる3ヘッダを提供します(CDN Cache docs)。

// app/api/catalog/route.ts
export async function GET() {
  return Response.json(await getCatalog(), {
    headers: {
      "Cache-Control": "public, max-age=10",        // ブラウザ:10秒
      "CDN-Cache-Control": "public, s-maxage=60",   // 下流CDN:60秒
      "Vercel-CDN-Cache-Control": "public, s-maxage=3600", // Vercel:3600秒
    },
  });
}
ヘッダ制御先ブラウザに返るか
Cache-Controlブラウザ+(CDN指定がなければ)CDN
CDN-Cache-Control下流CDN・Vercel CDN(ブラウザは無視)
Vercel-CDN-Cache-ControlVercel CDN のみ❌(返らない・転送されない)

関数レスポンスを CDN にキャッシュするには s-maxage=N(任意で , stale-while-revalidate=Z)が必要です。CDN-Cache-Control を指定せず Cache-Control だけ書くと、Vercel はブラウザ送信前に s-maxage/stale-while-revalidate剥がします(ブラウザに内部キャッシュ方針を漏らさないため)。

キャッシュ可能条件(厳格)

レスポンスが CDN にキャッシュされるには、すべてを満たす必要があります。

  • リクエストが GET または HEADRange/Authorization ヘッダを含まない
  • レスポンスが 200/404/410/301/302/307/308
  • コンテンツ長 10MB 以下(ストリーミングは 20MB
  • Set-Cookie を含まない
  • Cache-Controlprivate/no-cache/no-store を含まない
  • Vary: * を含まない

AuthorizationSet-Cookie が鬼門:認証付き API がキャッシュされない最頻原因です。ログイン必須ページは基本キャッシュしない(または Vary とユーザー非依存部分の分離で設計)。

Vary でユーザー属性ごとに分ける

国・言語・デバイスで内容を変えつつキャッシュしたいときは Vary。Vercel は X-Vercel-IP-Country 等のリクエストヘッダをキャッシュキーに加えられます。

export async function GET(request: Request) {
  const country = request.headers.get("x-vercel-ip-country") ?? "unknown";
  return Response.json({ greeting: `Hello from ${country}` }, {
    headers: { "Cache-Control": "s-maxage=3600", Vary: "X-Vercel-IP-Country" },
  });
}

Vary は1つ増えるごとにキャッシュエントリが指数的に増える(=ヒット率が下がる)ので、本当に内容を変えるヘッダだけに絞ります。


Runtime Cache:関数内のデータ片をキャッシュ

レスポンス全体ではなく、関数ので「同じ fetch・同じ DB クエリ・同じ計算結果」を再利用したいとき。Runtime Cache はリージョンごとのエフェメラルな KV で、タグで無効化でき、Functions・Routing Middleware・Builds 間で共有されます。

// 高コストな計算を Runtime Cache に載せる(疑似コード)
import { getCache } from "@vercel/functions";

export async function GET() {
  const cache = getCache();
  const key = "exchange-rates";

  const cached = await cache.get(key);
  if (cached) return Response.json(cached); // データ片の再利用

  const rates = await fetchExchangeRatesFromUpstream(); // 高コスト
  await cache.set(key, rates, { tags: ["rates"], ttl: 300 });
  return Response.json(rates);
}

ISR や Cache-Control が「レスポンス全体」を扱うのに対し、Runtime Cache は「個別のデータ片」を扱うのが本質的な違いです。両者は併用できます(ページは ISR、ページ内の一部 fetch は Runtime Cache)。


デバッグ:x-vercel-cache を読む

「キャッシュされているつもりで、実は毎回関数が走っている」を防ぐ唯一の方法は、x-vercel-cache ヘッダを実際に見ることです。

curl -sI https://your-app.vercel.app/api/catalog | grep -i x-vercel-cache
# x-vercel-cache: HIT
意味
HITCDN キャッシュから配信(関数は走っていない)
MISSキャッシュになく、オリジン(関数)で生成
STALE期限切れだがステイルを配信、裏で再検証中
PRERENDERビルド時に事前生成された静的コンテンツ

本番リリース前に、キャッシュさせたいパスが HIT/PRERENDER になっているかを必ず確認してください。これが Active CPU 課金とレイテンシを直接左右します(コスト最適化ガイド)。


本番チェックリスト(キャッシュ)

  • キャッシュ層を4つから意図的に選択している(なんとなく SSR ではない)
  • ISR の revalidate を要件に合わせて設定、人気ページは generateStaticParams
  • オンデマンド再検証エンドポイントを認可している
  • 関数キャッシュは s-maxage を付け、3ヘッダの役割を理解
  • 認証付きレスポンスを誤ってキャッシュしていない(Authorization/Set-Cookie)
  • Vary は本当に内容を変えるヘッダだけ
  • x-vercel-cacheHIT/PRERENDER実測確認
  • CWV(LCP/INP/CLS)を Speed Insights で監視(CWV 最適化

まとめ

キャッシュは「速さ」だけでなく、コスト(Active CPU を呼ばない)と可用性(再検証失敗時もステイル配信) を同時に作ります。

  1. 層を選ぶ:ページは ISR、API は CDN Cache、データ片は Runtime Cache、静的は自動
  2. ISR の自動最適化(31日永続・300ms purge・リクエスト併合・ロールバック耐性)を活かす
  3. オンデマンド再検証で即時反映、Cache Components でシェルと穴を両立
  4. 3ヘッダで階層制御し、キャッシュ可能条件を満たす
  5. x-vercel-cache で必ず実測する

次は、作ったものを安全に出す デプロイ・CI/CD・ロールバック ガイド へ。

本記事は ISR / CDN Cache / Runtime Cache 公式ドキュメント(2026年6月時点)に基づきます。仕様は更新されるため、本番採用時は公式で最新値を確認してください。

友田

友田 陽大

Pengembang produk peraih Penghargaan Menteri (METI). Dengan TypeScript + Python + AWS, saya mengerjakan SaaS, transformasi digital industri, dan AI generatif (RAG) siap produksi secara menyeluruh — dari perumusan kebutuhan hingga infrastruktur dan operasional, seorang diri.

I can take on the implementation from this article as an engagement

Vercel apps, from design to production and cost optimization

Function design assuming Fluid Compute (safe global state, waitUntil, Cron), four-layer caching (ISR/CDN/Runtime Cache/Cache Components), safe deploys (preview/Promote/Instant Rollback/Rolling Releases), entry-point defense (Firewall/WAF/BotID), storage selection (Blob/Edge Config/Marketplace), and Active-CPU-billing-aware cost optimization. With experience running Next.js products on Vercel in production, I deliver fast, cheap, and secure.

Tersedia untuk pekerjaan berbasis proyek maupun sebagai penasihat teknis. Mulailah dengan konsultasi gratis 30 menit.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Layak dibaca juga