本文へ移動
WEB 制作・集客

Next.js の use cache でユーザーごとのデータを混ぜない。キャッシュの境界の決め方と、セキュリティリリースの見方

公開 更新 読了まで 約13分

( この記事の要点 )

  1. 2026年11月3日のセキュリティリリースの修正版は 16.4.5 と 15.5.29。npm ls next で実際に入っている版を確かめ、修正版以降に上げる
  2. use cache のキーは関数の ID と引数(外側の変数を含む)。キーに入らない違いは同じ箱に入るので、ユーザー ID は cookies() をキャッシュの外で読んで引数で渡す
  3. クッキーを中で読みたいなら 'use cache: private'。CDN では Cache-Control を上書きせず、_rsc をキャッシュのキーに入れる

「この 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 で実際に動かして確かめたコードを載せます。

2026年11月3日のセキュリティリリースで直ったこと

公式ブログ「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 にする回避策が書かれています。

まず影響を見て、上げる

セキュリティリリースが出たときの手順は、今回に限らず同じです。

  1. 使っている版を確かめる。 package.json の範囲指定ではなく、実際に入っている版を見ます。
    npm ls next
    
  2. 条件を読む。 上の表の「主な条件」と、自分のプロジェクトの設定(cacheComponents、partialPrefetching、セルフホストかどうか、Edge のルートの有無)を照らします。当てはまらなくても、上げない理由にはしません。条件の読み違いは起きるからです。
  3. 修正版以降に上げる。
    # 16 系
    npm install [email protected]   # 16.4.5 以降(2026年11月10日時点の latest は 16.4.6)
    # 15 系
    npm install [email protected]
    
  4. 確かめる。 next build が通ること、会員ページを2つの別のアカウントで開いて、相手の情報が出ないことを見ます。CDN を前に置いているなら、CDN のキャッシュも消しておきます。
セキュリティリリースが出たときの4つの手順。1 版を確かめる、2 条件を読む、3 修正版以降に上げる、4 2つのアカウントで確かめる。
セキュリティリリースが出たときの手順。版番号は本文を見てください。

use cache は「全員で共有する箱」

境界を考える前提として、use cache の箱がどう分かれるかを押さえておきます。公式ドキュメントでは、キャッシュのキーは入力をシリアライズしたものから作られ、次のものが入ります。

  • ビルドの ID
  • 関数の ID
  • シリアライズできる引数
  • 外側のスコープから使った変数(自動で引数として扱われ、キーに入る)

root param([lang] のようなルートの動的な部分)は、関数の中で実際に読んだものだけがキーに入ります。

裏を返すと、キーに入っていない違いは、同じ箱に入ります。 ユーザー A と B で中身が違うのに、キーに違いがなければ、A の結果が B に返ります。今回の1〜3は、キーの作り方の側の不具合でしたが、同じことはアプリの書き方でも起こせてしまいます。

NG と OK の比べ。NG:alice と bob が同じ関数を呼ぶ。キーは「関数ID」だけで、userId は「なし」。結果は「alice の値」の箱1つで、「bob にも返る」。OK:キーに「関数ID」と「userId」が入り、「alice の値」と「bob の値」の2つの箱に分かれる。
キーにユーザーを区別する値がないと、別の人の結果が返る。説明用の作例です。

また、use cache の中では cookies()・headers()・searchParams を読めません。呼び出した先の関数でも同じです。リクエストごとに違う値を箱の中でこっそり読めないようにして、違いを必ず引数(=キー)に出させるための決まりだと考えると、設計の筋が見えてきます。

境界の決め方:use cache/private/remote の使い分け

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 か、キャッシュしないかのどれかです。

コード:cookies() は外で読み、引数で渡す

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つあります。

  • ユーザーを区別する値は、文字列か数値で渡す。 今回の3はバイナリの引数の識別子が重なる不具合でした。ID は素直な値で渡すほうが、キーの中身を人が確かめやすくなります。
  • unstable_cache の中から use cache の関数を呼ばない。 今回の1の条件で、アドバイザリでは、この入れ子をやめることが回避策として書かれています。移行は関数ごとに切り替えます。

CDN の前に置くとき:Cache-Control と Vary

動的なページの応答には、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 の関数ごとに「別の人に見せても困らないか」を答えられる
  • ユーザーごとの関数は、ユーザー ID を文字列の引数で受け取るか、private になっている
  • use cache: remote にユーザー固有のデータを入れていない
  • unstable_cache の中から use cache の関数を呼んでいない
  • セルフホストなら partialPrefetching を明示している
  • CDN で Cache-Control を上書きしていない、_rsc をキーに入れている
  • 2つのアカウントで会員ページを開き、相手の情報が出ない

キャッシュの境界は、レビューで「この関数の結果を誰に見せてよいか」を1行コメントで残しておくと、次に触る人も迷いません。MODS BASE でも、会員機能のあるサイトの設計や見直しをお手伝いしています。

( Written by )

MODS BASE

MODS BASEは、長野県上伊那郡辰野町を拠点に、WEB 制作、WEB 集客の運用代行、UI/UX デザイン・プロダクト開発、ブランディング・グラフィック、AI 導入支援を手がけるデザイン・開発スタジオです。全国のお客様にオンラインで対応しています。

( Related service )

WEB 制作

何を伝え、どう動いてもらうか。UX の設計から考えるホームページ制作。

( Related )

関連記事

( Contact )

Let’s talk.

まだ決まっていなくても大丈夫です。気になっていることから、気軽にお聞かせください。