はじめに
Next.js v16 には use cache: private というディレクティブがあり、cookies() などリクエスト固有の値を読んだ関数の結果をキャッシュできます。
その結果がサーバーに残らないということはドキュメントに書かれています。
In production, matching calls within one request can reuse the same result, but Next.js does not store it in a server cache across requests.
これを読むと use cache: private はあくまでも「一つのリクエスト内で同じ関数を複数回呼び出したら結果を再利用する」というものであって、リクエスト間では結果が共有されないようです。
しかしながら同ページには下記のようにも書かれています。
The client router can keep the rendered output in browser memory for the stale time configured with cacheLife. This client-side cache does not persist across page reloads.
レンダリング結果はブラウザに保存され得て stale time までは残るらしいです。つまり同一ブラウザで操作する限りはリクエスト間でキャッシュが使いまわされる場合があります。
文面的には必ず保存されるわけではなさそうなのですが、どういうときに保存されるのかがよくわからなかったので実際に動かして確かめました。
検証環境
- Next.js 16.3.8
next build && next start(本番モード)で検証しました。
また以降の検証における画面遷移は Next.js の <Link> だけで行っており、ブラウザの進む / 戻るボタンに関しては検証していません。
キャッシュはどういうときにブラウザに残るのか
検証用のコード
cookie の値を use cache: private でキャッシュする関数を作りました。キャッシュがいつ作られたかが分かるように生成時刻も返しています。
後述するのですが stale time は 5 分に一つの区切りがありそうなので 5 分にしました。
import { cacheLife } from "next/cache";
import { cookies } from "next/headers";
export async function getSessionValue() {
"use cache: private";
cacheLife({ stale: 300 });
const session = (await cookies()).get("session")?.value ?? "(なし)";
return { session, generatedAt: new Date().toISOString() };
}
この関数の結果を表示するページを /cookie と /cookie/other の2つ用意し、画面には「キャッシュされた cookie&そのキャッシュの生成時刻」と「クライアントサイドの useEffect 内でブラウザから読み取った cookie」を並べて表示しました。

cacheComponents: true だけではブラウザに残らなさそう
まず next.config.ts を cacheComponents: true だけにして試しました。
// next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
};
/cookie を開いて cookie の値をキャッシュに保存し、<Link> で /cookie/other → /cookie → /cookie/other と行き来しました。
その結果、表示されるキャッシュの生成時刻は毎回異なっていました。
少なくとも stale time が 5 分では、Cache Components を有効にしただけではブラウザに保存されないように見えます。
partialPrefetching: true では stale time によってはブラウザに残る
prefetching の説明 によると、Partial Prefetching を有効にすると各ルートの App Shell が prefetch され、そこに cookie 由来のデータも含まれるようです。
With Partial Prefetching enabled, the router prefetches each route's App Shell by default. The App Shell includes static content and session data derived from
cookies()andheaders().
また glossary の App Shell の項によると、stale が5分以上のキャッシュが App Shell に入ります。
Cached content is included when its stale time is at least 5 minutes, since the shell is reused for longer than shorter-lived content stays fresh.
これは関係ありそうなので、partialPrefetching: true を追加してみます。
※Partial Prefetching は前提として Cache Components が必要です。
// next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
};
同じ手順で試した結果が以下です。stale time は前回同様 5 分です。
| 操作 | 表示されたキャッシュ生成時刻 |
|---|---|
/cookie を読み込み |
新しく作られた |
/cookie/other へ遷移(1回目) |
新しく作られた |
/cookie へ遷移 |
最初の読み込みのときの値 |
/cookie/other へ遷移(2回目) |
1回目の遷移のときの値 |
5分以上待って /cookie と /cookie/other へ遷移 |
新しく作られた |
同じパスへの2回目以降の遷移ではブラウザに残ったキャッシュが使われました。
stale time が過ぎるとキャッシュが作り直されることも確認できました。
特定の状況下では use cache: private のキャッシュがブラウザに残ることがわかりました。
そうであれば、cookie を元にした値を use cache: private でキャッシュした場合、cookie を消した後にキャッシュも消さないとデータが漏洩しそうです……が、念の為確認しました。
cookie を消す方法を3つ用意しました。
- A. Server Action で消す(
<form>の submit で React Server Function を呼ぶ) - B. Route Handler で消す(ボタンクリック時に fetch する)
- C. クライアントサイドで消す(ボタンクリック時に document.cookie を直接操作する)
// A. Server Action
"use server";
export async function logoutByAction() {
(await cookies()).delete("session");
}
// B. Route Handler
export async function POST() {
(await cookies()).delete("session");
return new Response(null, { status: 204 });
}
// C. クライアントサイドの JS
document.cookie = "session=; path=/; max-age=0";
※C を試すために検証用の cookie は httpOnly: false にしました。
手順
-
/cookieを開いて Server Action で cookie を設定する。 -
<Link>で行き来して、キャッシュが効いていることを確かめる。 - A〜C のどれかで cookie を消す。
- もう一度
<Link>で行き来して、表示される値を見る。
結果
| cookie の消し方 | 結果 |
|---|---|
A. Server Action で cookies().delete()
|
古い値はすぐ消えた |
B. Route Handler で cookies().delete()
|
古い値が残った |
C. document.cookie で削除 |
古い値が残った |
B か C のあとに router.refresh()
|
古い値が消えた |
古い値が出たのは cookie を消す前に一度表示したページに限ります。表示せず prefetch していただけのページは、最初の遷移でサーバーに取りに行き、その時点の cookie で描画されました。
上記のように Server Action だけ特殊な挙動だったのですが、この件はドキュメントに明記されていました(Mutating Data)。
When you set or delete a cookie in a Server Action, Next.js re-renders the current page and its layouts on the server so the UI reflects the new cookie value.
別のドキュメント にも同様の記載があります。
After you set or delete a cookie in a Server Function, Next.js can return both the updated UI and new data in a single server roundtrip when the function is used as a Server Action (e.g., passed to a form's action prop).
こちらの文面では、あくまでも Server Action として Server Function を使ったときだけ UI が更新されるということが明示されています。
まとめ
use cache: private のキャッシュは、少なくとも Partial Prefetching が有効&stale time が 5 分以上だとブラウザに残る場合があります。
「stale time 内に同じブラウザで別の人が操作する」というシナリオはあり得なくはないので、ログアウト後は window.location.href = "/login" のようにページ全体を読み込み直す等の対策をすると安心だと思います。
ちょっと株式会社のエンジニアブログ。Next.jsによる大規模Webサイト制作やAIアプリケーション開発が得意とし、日本初のVercel公式パートナーです。 会社情報:chot-inc.com/ カジュアル面談の申し込み: chot-inc.com/recruit/iuj62owig
Discussion
