Next.js 16 になって、キャッシュの考え方が大きく変わりました。これまでの「fetch がデフォルトでキャッシュされる」モデルから、「明示的に use cache と書いた箇所だけキャッシュする」モデルへの転換です。
この変化は、業務システムを受託で作っている私たちのような開発会社にとっては、むしろ歓迎すべき変更だと感じています。何がキャッシュされていて、何が毎回サーバーで実行されているのか、コードを見ただけで判断できるようになるからです。
一方で、中小企業向けのフルスクラッチで「どこに use cache を入れるべきか」「cacheLife と cacheTag をどう使い分けるか」「更新した直後の画面をどう最新にするか」は、Next.js 公式ドキュメントを読んだだけでは判断しづらい部分でもあります。本記事では、私たちが社内・受託案件で使っている判断基準をまとめます。
先に、要点をまとめます(2026年10月9日確認)。
use cache は、next.config で cacheComponents: true を設定したときに使える機能です。2026年10月公開の Next.js 16.4 から、この仕組み(Cache Components)が全アプリに推奨され、Next.js 17 では既定で有効になります
- キャッシュするのは全ユーザー共通のデータに絞り、
cacheLife で寿命を、cacheTag で業務イベントによる破棄を決めます。利用者が更新した内容をすぐ見せたい画面では、Server Action から updateTag を呼びます
use cache の保存先は、既定ではサーバーのメモリです。Firebase App Hosting のようなサーバーレスの環境では、インスタンスごとに分かれ、デプロイのたびに消える前提で設計します
use cache を使うには、何を有効にする?
next.config で cacheComponents: true を設定します。設定していないアプリでは use cache は動きません。Next.js 16.4 からは、あわせて partialPrefetching: true も設定するよう案内されています。
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
};
export default nextConfig;
公式の use cache のドキュメントは、use cache を Cache Components の機能の1つとして説明しています。Next.js 16.0 で安定版になり、16.4 の公式ブログでは、Cache Components をすべてのアプリに勧める方針と、Next.js 17 で既定にする予定が示されました。
すでに動いているアプリで有効にすると、ルートに書いた export const revalidate や export const dynamic などがbuildエラーになります。当社のサイトで試すと7件のエラーで止まりました。止まる箇所の洗い出し方と切り替えの順番は、Next.js 16.4の変更点と既存アプリで今やることにまとめています。
なお、2026年10月14日に、use cache の関数が複数のtyped arrayを返すときに、認証なしでリモートコード実行につながる脆弱性(GHSA-673q-mjm6-r5xr、Critical)が公表され、16.4.1と15.5.28で修正されました。回避策はなく、公式は、似た関数がないアプリでもCache Componentsを使っていればすぐ更新するよう案内しています。use cache を入れる前に、まず16.4.1以上へ上げておきます(公式の告知)。
Next.js 16 のキャッシュは、何が変わった?
ページ・レイアウト・API ルートのコードは、既定でリクエストのたびに実行されます。キャッシュしたい関数やコンポーネントには、先頭に "use cache" を書いて明示します。
Next.js 16 では、ページ・レイアウト・API ルートのコードはすべて、デフォルトでリクエスト時に実行されます(Next.js 16 公式ブログ)。何かを「キャッシュしたい」と思った場合は、関数やコンポーネントの先頭に "use cache" を書いて、明示的にキャッシュ対象であることを宣言します。キャッシュする関数とコンポーネントは async である必要があります。
// app/lib/products.ts
import { cacheLife } from "next/cache";
export async function getFeaturedProducts() {
"use cache";
cacheLife("hours");
const products = await db.product.findMany({
where: { featured: true },
orderBy: { updatedAt: "desc" },
});
return products;
}
このシンプルさが効いてきます。コードレビューで「ここはキャッシュされていますか?」という議論が、関数の先頭を見るだけで終わるようになりました。
どこに use cache を入れるか、何で判断する?
全ユーザー共通か、どのくらいの頻度で更新されるか、古いデータを見せてよいか、の3つで判断します。ログインしている人ごとに結果が変わるデータは、原則としてキャッシュしません。
軸1: 全ユーザー共通か、ユーザー個別か
全ユーザーで同じ結果を見せてよい関数は use cache の候補です。たとえば「公開中の商品一覧」「企業の問い合わせフォームに表示する選択肢マスタ」「公開記事のリスト」など。
逆にログインユーザーごとに結果が変わるもの、たとえば「自分の発注履歴」「自分の権限で見える顧客一覧」は、原則キャッシュしません。どうしてもまとめたい場合の選択肢は、後述の use cache: private です。
軸2: 更新頻度がどのくらいか
1日に数回しか更新されないデータと、1分ごとに変わるデータでは、キャッシュ戦略がまったく違います。
- 商品マスタ・カテゴリ一覧 → 1日数回 →
cacheLife("hours")
- 在庫数・受注ステータス → 数分単位 →
cacheLife("minutes") or 都度更新
- ダッシュボードの集計値 → 1日1回バッチ更新 →
cacheLife("days")
軸3: 古いデータを見せてよいか
ECの商品説明であれば、数分古いキャッシュを見せても大きな問題にはなりません。一方、銀行の残高や予約システムの空き枠は、1秒でも古いデータを見せられないケースがあります。
「どれくらい古いデータならOKか」を業務側にヒアリングして決めるのが、いちばん確実です。

cacheLife と cacheTag は、どう使い分ける?
cacheLife は時間で切れる寿命、cacheTag は業務のイベントで破棄するための目印です。業務データでは両方を付け、寿命の上限を決めたうえで、更新があったときにタグで破棄する組み合わせが運用しやすい形です。
cacheLife: 時間ベースの有効期限
cacheLife はキャッシュの寿命を決めるための関数です。Next.js が用意した "seconds" "minutes" "hours" "days" "weeks" "max" のプロファイルを使うか、独自のプロファイルを定義できます。
import { cacheLife } from "next/cache";
export async function getCategoryList() {
"use cache";
cacheLife("hours");
return await db.category.findMany({ where: { active: true } });
}
cacheLife を省略すると default プロファイル(サーバー側の再検証は15分、時間による期限切れはなし)が使われます。公式ドキュメントは、use cache を書いたすべての箇所で cacheLife を明示するよう勧めています。読む人がその場で寿命を確認できるためです。
中小企業の業務システムでは、まずプリセットの "minutes" "hours" で十分なことが多いです。next.config.ts で独自プロファイルを定義する必要が出てくるのは、独特の更新頻度を持つデータ(毎週月曜の朝に更新される、など)に当たったときです。
cacheTag: 業務イベント起点で無効化する
cacheTag は、キャッシュにタグを付けて、後から updateTag や revalidateTag で明示的に無効化できる仕組みです。
import { cacheLife, cacheTag } from "next/cache";
export async function getProductDetail(productId: string) {
"use cache";
cacheLife("hours");
cacheTag(`product-${productId}`, "products");
return await db.product.findUnique({ where: { id: productId } });
}
私たちの実務感覚では、cacheLife だけで足りるケースは少なく、ほとんどの業務データには cacheTag も併用します。「業務側のアクションでデータが更新された瞬間に、UIに反映したい」というニーズがほぼどの案件にもあるからです。
更新したデータを、すぐ画面に出すには?
利用者が画面から更新した内容をその人にすぐ見せるなら、Server Action の中で updateTag を呼びます。外部のシステムからの通知や定期処理で更新するなら、Route Handler から revalidateTag(tag, "max") を呼びます。
Next.js 16 では、無効化の関数が用途で分かれました。公式の updateTag・revalidateTag のドキュメントをもとにまとめると次のとおりです。
| 関数 | 呼べる場所 | 次のリクエストで起きること | 向いている場面 |
|---|
updateTag(tag) | Server Action のみ | 新しいデータを取得するまで待つ。古い内容は出さない | 管理画面で商品を更新した直後、本人に結果を見せる |
revalidateTag(tag, "max") | Server Action・Route Handler | いったん古い内容を返し、裏で新しいデータを取りにいく | 外部システムからのWebhook、夜間の定期更新 |
revalidateTag(tag, { expire: 0 }) | Server Action・Route Handler | 古い内容は出さず、次のリクエストで取り直す | Webhookで受けた変更を、次の表示から反映したい |
商品マスタを管理画面で更新する場合は、Server Action から updateTag を呼びます。
"use server";
import { updateTag } from "next/cache";
export async function updateProduct(productId: string, data: ProductInput) {
await db.product.update({ where: { id: productId }, data });
// この商品だけ破棄
updateTag(`product-${productId}`);
// 一覧系も破棄
updateTag("products");
}
外部のシステムから「在庫が変わった」という通知を受ける場合は、Route Handler で受けて revalidateTag を呼びます。updateTag は Server Action 以外で呼ぶとエラーになります。
// app/api/webhooks/inventory/route.ts
import { revalidateTag } from "next/cache";
export async function POST(request: Request) {
// 送信元の検証(署名の確認など)は省略しています
const { productId } = await request.json();
revalidateTag(`product-${productId}`, "max");
return Response.json({ ok: true });
}
第2引数を付けない revalidateTag(tag) は、Next.js 16 で非推奨になりました。公式ドキュメントは、現在は動いても将来の版で削除される可能性があるとしています。15系から移行したコードに残っていたら、updateTag か第2引数付きの呼び出しに書き換えます。Webhook の受け口には、署名の確認など送信元の検証を必ず入れてください。
use cache の結果は、同じ引数で呼んだすべての利用者に使い回されるためです。cookies() や headers() はキャッシュする関数の外で読み、値を引数として渡します。
use cache で囲んだ関数の中では cookies() headers() searchParams といったリクエスト時のAPIを直接呼べません(Next.js 公式 use cache ドキュメント)。キャッシュする関数から呼んだ別の関数の中で読んでいても、同じエラーになります。
これらの値はキャッシュ関数の外で取得し、引数として渡す設計に変えます。
// ✗ NG: use cache 内で cookies を呼ぼうとしている
export async function getMyOrders() {
"use cache";
const cookieStore = await cookies();
const userId = cookieStore.get("userId")?.value;
return await db.order.findMany({ where: { userId } });
}
// ○ OK: 引数として userId を受け取る
async function getOrdersByUser(userId: string) {
"use cache";
cacheLife("minutes");
cacheTag(`orders-${userId}`);
return await db.order.findMany({ where: { userId } });
}
// 呼び出し側(Server Component)で cookies を読んで渡す
export async function MyOrdersPage() {
const cookieStore = await cookies();
const userId = cookieStore.get("userId")?.value;
if (!userId) redirect("/login");
const orders = await getOrdersByUser(userId);
return <OrderList orders={orders} />;
}
「ユーザー個別データはキャッシュしない」という大原則と組み合わせると、use cache を入れる位置に迷うことがそもそも減ります。
use cache: private はいつ使う?
use cache: private は、関数の中で cookies() などを読む必要があり、引数で渡す形に直せない場合の選択肢です(公式ドキュメント)。結果はサーバーのキャッシュには保存されず、同じリクエストの中での使い回しと、ブラウザのメモリでの保持(cacheLife の stale で指定した時間)に限られます。個人の情報をサーバー側のキャッシュに残したくない、という要件にも合います。公式ドキュメントは、通常は引数で渡す形を推奨しているため、私たちも最後の手段として扱っています。
サーバーレスの環境では、キャッシュはどこに残る?
既定の保存先はサーバーのメモリで、サーバーレスでは処理するインスタンスごとに分かれます。デプロイのたびにキャッシュのキーが変わるため、どの環境でもデプロイ後は作り直しになります。
公式ドキュメントは、サーバーレスの環境では実行時のキャッシュがリクエストをまたいで残らないことが多く、セルフホストのサーバーでは残る、と説明しています。Firebase App Hosting も、実行環境は Cloud Run のサービスです。データベースへのアクセスを減らしたくて use cache を入れても、アクセスが少ない時間帯にはインスタンスが入れ替わり、期待したほど使い回されないことがあります。
そのため、次のどれを選ぶかを先に決めておきます。
- build 時に作れるページは静的に配信する。キャッシュした結果は build 時に作るページにも含まれる
use cache: remote や cacheHandlers で、Redis や KV のような外部の保存先を使う(保存先の費用と通信の時間がかかる)
- デプロイをまたいで残したいデータは、
unstable_cache か fetch のキャッシュに置く。公式の移行ガイドでも、この2つは移行しなくてよいとされている
中小企業向けの現実的な初期設計は?
キャッシュなしで動くものを作り、マスタ系から use cache を足していきます。個人別のデータは原則キャッシュせず、効いているかはログで確かめてから広げます。
新規プロジェクトを立ち上げるとき、私たちは次の方針でスタートしています。
- デフォルトはキャッシュなし。
cacheComponents を有効にしたうえで、use cache を書いた関数だけがキャッシュ対象
- マスタ系(商品・カテゴリ・顧客区分など)は
cacheLife("hours") + cacheTag。管理画面からの更新は updateTag で破棄
- ダッシュボードの集計値は
cacheLife("days")。夜間の集計処理が終わったら、Route Handler 経由で revalidateTag(tag, "max") を呼ぶ
- 個人別データは原則キャッシュしない。必要なら引数で渡す形にするか、
use cache: private を検討
- 本番リリース後、キャッシュが使い回されているかを確かめる。公式ドキュメントにある
NEXT_PRIVATE_DEBUG_CACHE=1 を付けて起動するとキャッシュの詳しいログが出るので、検証環境でアクセスを流して、ヒットしない関数から見直す
最初から完璧なキャッシュ設計を目指すと、設計コストが膨らみます。中小企業の予算感では「まずキャッシュなしで動くものを作って、ボトルネックが見えてから足す」アプローチのほうが合っていることが多いです。
SaaS から移行するとき、何が変わる?
SaaS では見えなかった「いつ、何がキャッシュされ、いつ破棄されるか」を、自社のコードで決められるようになります。業務のイベントに合わせて破棄の仕組みを組めるのが、フルスクラッチの利点です。
WordPress や既存 SaaS から自社専用システムに切り替える案件で、Next.js 16 のキャッシュ設計の良さを実感する場面があります。SaaS では「裏側のキャッシュ挙動が見えない」「無効化のタイミングを制御できない」ことが運用上のストレスになっていることが多いのですが、フルスクラッチで作れば、業務イベント起点で無効化する仕組みを自分たちで握れます。
このあたりの判断軸については、受発注管理を SaaS から Next.js フルスクラッチに切り替える判断基準、中小企業の「SaaS疲れ」を解消する|Next.js フルスクラッチという選択肢も併せて参考になると思います。社内ダッシュボード設計の観点は散らばった社内データを1枚のダッシュボードに集約する|Next.js × Firebaseの設計、AI連携を組み合わせる場合は【2026年版】Next.js × AIエージェントで業務を自動化する実務ガイドが参考になります。
まとめ
use cache を使うには cacheComponents: true が必要です。Next.js 16.4 から全アプリに推奨され、17 で既定になります
- キャッシュするのは全ユーザー共通のデータに絞り、
cacheLife で寿命、cacheTag で業務イベントによる破棄を決めます
- 画面からの更新は Server Action で
updateTag、外部からの通知は Route Handler で revalidateTag(tag, "max") を使います。1引数の revalidateTag は非推奨です
- サーバーレスの環境では、キャッシュはインスタンスごとに分かれ、デプロイで消えます。残したいデータの置き場所を先に決めます
既存のアプリでどこにキャッシュを入れるか、Cache Components への切り替えをどこから始めるかを、実際のコードを見ながら整理したい方は、15分の壁打ちからご相談を受け付けています。
キャッシュ設計とあわせて、Next.js 開発そのもののパフォーマンスを底上げしたい方には、Core Web Vitals改善の実務手順|Next.jsチェックリスト、Next.jsのパフォーマンス改善はどこまで自動? 実装で差がつくポイントを整理もあわせてご覧ください。use cache だけでなく、レンダリング全体の最適化まで含めて中小企業のフルスクラッチ案件に落とし込む視点が整理できるはずです。
※本記事は、2026年10月9日時点の use cache、updateTag、revalidateTag、Cache Componentsへの移行ガイド の各ドキュメント(16.4.0版)に基づいています。脆弱性の記述は2026年11月3日時点の公式の告知に基づきます。
技術仕様・対象バージョンは本文と参照先をご確認ください。
最終更新:2026年11月3日