はじめに
Next.js 16でキャッシュ周りが変わったらしいが全然追えていなかったので今回はuse cacheに関して整理してみました。
キャッシュは1箇所じゃない
前提知識としてWebアプリケーションの「キャッシュ」は1つの場所だけに存在するものではありません。
Browser
⇩
CDN / Edge
⇩
Application Server
⇩
Database / API
上記のようにブラウザにはHTTPのBrowser Cacheがあり、CloudflareのようなCDNにはEdge Cacheがあります。そしてNext.js自身にもデータの再利用やレンダリング結果の再利用を行う仕組みがあります。
Cache Componentsとは何か
Cache ComponentsはこれまでNext.jsのApp Routerが自動的にキャッシュするような仕組みではなく、明示的にuse cacheを指定して「キャッシュしたいものだけ自分で宣言する」ものになります。
use cacheの基本
use cacheはファイル・関数・コンポーネントの先頭に書くディレクティブです。付ける場所によってキャッシュされる範囲が変わります。
①関数の先頭に書く
その関数の戻り値だけがキャッシュされます。
async function getProducts() {
"use cache"
const res = await fetch("https://api.example.com/products")
return res.json()
}
②コンポーネントの先頭に書く
そのコンポーネントのレンダリング結果がキャッシュされます。
async function ProductList() {
"use cache"
const products = await getProducts()
return (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
)
}
③ファイルの先頭に書く
そのファイルからexportされるすべての関数がキャッシュ対象になる
cacheLife / cacheTag
特に指定をしない場合、キャッシュの寿命はデフォルト値になりますが、cacheLifeとcacheTagを組み合わせることで寿命と無効化のタイミングを制御することができます。
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from "next/cache"
async function getProducts() {
"use cache"
cacheLife("hours")
cacheTag("products")
const res = await fetch("https://api.example.com/products")
return res.json()
}
・cacheLife(profile): "seconds" / "minutes" / "hours" / "days" / "weeks" / "max" のようなプロファイル名、もしくは独自の設定でキャッシュの寿命を指定する
・cacheTag(tag): キャッシュにタグを付けておき、revalidateTag(tag)を呼び出すことでオンデマンドに再検証(=キャッシュ破棄)できる
上記の例では「商品情報を更新した直後に反映したい」というようなケースでは更新処理の中でrevalidateTag("products")を呼べば、次のアクセス時に最新データへ切り替わります。
キャッシュしない動的な処理とSuspense
Cache Componentsは「デフォルトが動的」なのでcookies()やheaders()、searchParams、キャッシュしていないfetchなど、動的にしか読めない値をコンポーネントの中で使う場合は、その部分を<Suspense>で囲む必要があります。
import { Suspense } from "react"
import { cookies } from "next/headers"
async function UserGreeting() {
const cookieStore = await cookies()
const name = cookieStore.get("username")?.value ?? "ゲスト"
return <p>こんにちは、{name}さん</p>
}
export default function Page() {
return (
<div>
<ProductList /> {/* use cacheでキャッシュ済み:静的な殻として先に届く */}
<Suspense fallback={<p>読み込み中...</p>}>
<UserGreeting /> {/* 動的:Suspense内でストリーミングされる */}
</Suspense>
</div>
)
}
ページ全体を「キャッシュできる静的な殻」と「リクエストごとに変わる動的な部分」に分け、動的な部分だけ後からストリーミングで届けるというのがPartial Prerenderingの考え方のようです。 参照
どういうデータをキャッシュすると良いか
結局どこでuse cacheを使えばいいのか?悩むので以下で判断基準を整理してみました。
| データの性質 | 例 | 方針 |
| --- | --- | --- |
| 更新頻度が低い | 商品マスタ・記事・カテゴリ一覧 | `use cache`と相性がいい |
| ユーザーによって内容が変わる | ユーザーごとのプロフィール | 安易に共有キャッシュしない |
| 常に最新性が重要 | 現在時刻・リアルタイム在庫 | キャッシュしない、または短い`cacheLife` |
| 多少古くても問題ない | 記事一覧 | `cacheLife`を設定してキャッシュ |
ポイントは「ユーザーによって結果が変わるかどうか」と「多少古くても許容できるかどうか」の2軸で考えることだと思います。ユーザー固有の値をうっかり`use cache`で包んでしまうと、他のユーザーに同じキャッシュ結果を返してしまう危険があるので、その点は特に注意が必要そうです。
まとめ
- Next.js 16のCache Componentsは、「放っておくとキャッシュされる」から「キャッシュしたいものだけ明示する」モデルへの転換
- `"use cache"`をファイル・関数・コンポーネントの先頭に付けることでキャッシュ範囲を指定する
- `cacheLife`で寿命を、`cacheTag` + `revalidateTag`でオンデマンド再検証を制御する
- 動的にしか読めない値(cookies/headers/searchParamsなど)は`<Suspense>`で囲み、キャッシュできる静的な部分と切り分ける
- キャッシュすべきかどうかは「ユーザーによって結果が変わるか」「多少古くても許容できるか」で判断する
Browser CacheやCDN Cache、Client-side Router Cacheについては、また別の機会に整理したいと思います。