
AI 活用
公開 更新 読了まで 約13分

「この use cache、ユーザーの情報も入ってたっけ?」。2026年11月3日に公開された Next.js のセキュリティリリース(16.4.5 と 15.5.29)には、use cache と Cache Components まわりで、キャッシュを通じてほかの人のデータが見えたり、内容が汚されたりする問題の修正が4件入りました。会員ページのあるサイトで Cache Components を使い始めたところなら、まず気になるのはこの一点だと思います。
先に結論です。やることは2つあります。ひとつは、使っている版を確かめて修正版以降に上げること。もうひとつは、ユーザーごとのデータがどこまで共有のキャッシュに入ってよいか、境界をコードの形で決めておくことです。後者は、今回の不具合が直ったあとも、use cache を書くたびに使える考え方です。
この記事では、変わった点を1つの節にまとめたうえで、境界の決め方と、Next.js 16.4.6 で実際に動かして確かめたコードを載せます。
公式ブログ「November 2026 Security Release」に載っている7件です。深刻度はブログの表記です(1件目は GitHub のアドバイザリでは Moderate と書かれています)。
| # | 内容(要約) | ブログの深刻度 | 主な条件 |
|---|---|---|---|
| 1 | unstable_cache と use cache を組み合わせたときの、テナントをまたぐ漏えい |
High | Cache Components が有効で、同じ use cache 関数を直接呼ぶ場所と unstable_cache の中から呼ぶ場所があり、その関数が root param を読む |
| 2 | 入れ子の use cache で、root param の値をまたいで漏れる |
Medium | 入れ子の use cache で、内側の関数が root param を読み、先に返る値と遅れて返る非同期の値を持ち、外側の関数がその遅い値を待たずに終わる |
| 3 | バイナリの引数で、ユーザーをまたいだ漏えい・汚染 | Medium | 異なるバイナリの引数が、同じキャッシュの識別子になる |
| 4 | セルフホストでの共有シェルの汚染 | Medium | next start で Cache Components が有効、Partial Prefetching が無効か未設定で、特定のルートの形(build で作らなかった値を受ける動的パラメーターの下に、プリレンダーされないパラメーターが2つ以上あるなど)を持つ |
| 5 | 圧縮された応答の区切りが古いままになり、別のクライアントへ混入・漏えい | Medium | セルフホストで、外から操作できる fetch の Response をそのまま返し、パイプライン化した HTTP/1.1 のリクエストを共有のバックエンド接続に並べるリバースプロキシの後ろにある |
| 6 | Edge のルートで、内部のヘッダーからクッキーの値が見える | Medium | Edge の Route Handler などで NextResponse.cookies.set() を使う |
| 7 | 画像最適化で SVG の拒否をすり抜けられる | Medium | images.dangerouslyAllowSVG: false に頼っている |
表の条件は要約です。詳しい条件は、各アドバイザリ(参考に載せたもの、またはブログからのリンク)で確かめてください。
修正版は 16.4.5(Active LTS)と 15.5.29(Maintenance LTS)です。2026年11月10日時点の npm の latest は 16.4.6 です。15 系へのセキュリティ修正は、公式のサポート方針で2027年1月31日までと書かれています。
1〜4 は Cache Components を使っているときの話です。4 については、アドバイザリに next.config で partialPrefetching: true にする回避策が書かれています。
セキュリティリリースが出たときの手順は、今回に限らず同じです。
package.json の範囲指定ではなく、実際に入っている版を見ます。npm ls next
cacheComponents、partialPrefetching、セルフホストかどうか、Edge のルートの有無)を照らします。当てはまらなくても、上げない理由にはしません。条件の読み違いは起きるからです。# 16 系
npm install [email protected] # 16.4.5 以降(2026年11月10日時点の latest は 16.4.6)
# 15 系
npm install [email protected]
next build が通ること、会員ページを2つの別のアカウントで開いて、相手の情報が出ないことを見ます。CDN を前に置いているなら、CDN のキャッシュも消しておきます。
境界を考える前提として、use cache の箱がどう分かれるかを押さえておきます。公式ドキュメントでは、キャッシュのキーは入力をシリアライズしたものから作られ、次のものが入ります。
root param([lang] のようなルートの動的な部分)は、関数の中で実際に読んだものだけがキーに入ります。
裏を返すと、キーに入っていない違いは、同じ箱に入ります。 ユーザー A と B で中身が違うのに、キーに違いがなければ、A の結果が B に返ります。今回の1〜3は、キーの作り方の側の不具合でしたが、同じことはアプリの書き方でも起こせてしまいます。

また、use cache の中では cookies()・headers()・searchParams を読めません。呼び出した先の関数でも同じです。リクエストごとに違う値を箱の中でこっそり読めないようにして、違いを必ず引数(=キー)に出させるための決まりだと考えると、設計の筋が見えてきます。
use cache を書く前に、関数ごとに次の順で決めます。
| 問い | 答え | 書き方 |
|---|---|---|
| 1. 結果は全員に同じか | はい(お知らせ、商品一覧) | 'use cache'(共有)。サーバーレスで箱を保てない環境なら 'use cache: remote' |
| 2. ユーザーごとに違うが、違いを ID で表せるか | はい(ポイント、会員ランク) | cookies() は外で読み、ユーザー ID を文字列の引数で渡して 'use cache' |
| 3. クッキーやヘッダーそのものを中で読む必要があるか | はい(引数で渡す形に直せない) | 'use cache: private'(本番ではサーバーのキャッシュに保存されない) |
| どれでもない | 毎回の結果が必要(残高、カート) | キャッシュしない |
公式ドキュメントでも、ユーザー固有のデータには 'use cache: remote' ではなく 'use cache: private' を使うよう書かれています。remote は全員で共有する箱です。また公式では、private はコンプライアンス上の要件があるときや、引数で渡す形に直せないときに使う、まれな例外とされています。まずは2の形を考えます。
迷ったときは「この関数の結果を、別の人に見せても困らないか」を問います。困るなら、ユーザーを区別する値がキーに入っているか、private か、キャッシュしないかのどれかです。
Next.js 16.4.6、React 19.3.1、Node.js 22 で確かめたコードです。next.config.ts で Cache Components を有効にします。
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
}
export default nextConfig
会員ページです。cookies() はキャッシュの外のコンポーネントで読み、ユーザー ID を引数で渡します。
// app/account/page.tsx
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
// 引数の userId がキャッシュのキーに入る
async function getPoints(userId: string) {
'use cache'
cacheLife('minutes')
cacheTag(`points-${userId}`)
return { userId, points: userId.length * 10 } // 例:本当は DB から読む
}
async function Points() {
// 例として生のクッキーを使う。本番ではセッションを検証して得た ID を渡す
const userId = (await cookies()).get('uid')?.value
if (!userId) return <p>ログインしてください</p>
const { points } = await getPoints(userId)
return <p>{userId} さんのポイント:{points}</p>
}
export default function AccountPage() {
return (
<main>
<h1>マイページ</h1>
<Suspense fallback={<p>読み込み中</p>}>
<Points />
</Suspense>
</main>
)
}
next start で、クッキー uid=alice と uid=bob で開くと、それぞれ自分のポイントが返りました。use cache の関数の中で cookies() を読むように書き換えると、エラーになります。静的に組めるルートでは next build が止まり、この会員ページのように動的なルートでは、build は通って next start でリクエストしたときにエラーが出ます。手元で静的なルートで試したときは、build が次のエラーで止まりました。
Error: Route "/bad": `cookies()` can't be read inside `"use cache"`. Read it outside the cached function and pass what you need as an argument.
どうしてもクッキーの中身を関数の中で使う必要があるときは 'use cache: private' にします。こちらは中で cookies() を読めて、alice と bob でそれぞれの結果が返ることを確かめました。
async function getGreeting() {
'use cache: private'
cacheLife({ stale: 60 })
const userId = (await cookies()).get('uid')?.value ?? 'guest'
return `${userId} さん、こんにちは`
}
private は、リンクごとの先読みを効かせるには stale を30秒以上にする必要があります(公式ドキュメントより)。
あわせて守っておきたいことが2つあります。
unstable_cache の中から use cache の関数を呼ばない。 今回の1の条件で、アドバイザリでは、この入れ子をやめることが回避策として書かれています。移行は関数ごとに切り替えます。動的なページの応答には、Next.js が Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate を付けます。手元で確かめた会員ページの応答も、この値でした。CDN の設定でこれを上書きして「全部キャッシュ」にすると、アプリ側でどれだけ境界を守っても崩れます。
もうひとつは Vary です。Next.js は rsc、next-router-state-tree、next-router-prefetch、next-router-segment-prefetch(インターセプトするルートでは next-url も)を Vary に入れます。Vary に対応していない CDN のために _rsc というクエリが付くので、_rsc はキャッシュのキーに含めます(公式の「Using a CDN with Next.js」より)。
npm ls next で、修正版(16.4.5/15.5.29)以降が入っているuse cache の関数ごとに「別の人に見せても困らないか」を答えられるprivate になっているuse cache: remote にユーザー固有のデータを入れていないunstable_cache の中から use cache の関数を呼んでいないpartialPrefetching を明示しているCache-Control を上書きしていない、_rsc をキーに入れているキャッシュの境界は、レビューで「この関数の結果を誰に見せてよいか」を1行コメントで残しておくと、次に触る人も迷いません。MODS BASE でも、会員機能のあるサイトの設計や見直しをお手伝いしています。
MODS BASE
MODS BASEは、長野県上伊那郡辰野町を拠点に、WEB 制作、WEB 集客の運用代行、UI/UX デザイン・プロダクト開発、ブランディング・グラフィック、AI 導入支援を手がけるデザイン・開発スタジオです。全国のお客様にオンラインで対応しています。
( Related )