Prompt

"use server";
 
// ここでキャッシュさせたい
let userInfo = null;
 
async function getUser(userId: number) {
  if(userInfo) { // キャッシュがあれば新たに取得しない!
    return userInfo;
  }
  const user = await usersテーブル.select(id: userId);
  userInfo = user;
  return user;
}

これ、具体的に一体どんな悪いことが起きるんだ?

あはは!これは「Next.jsのServer Actions(またはRSC)」を学び始めた時に、誰もが一度は「これ、便利じゃん!」と閃いてしまう超危険なトラップだね!

情報工学的に言うと、これは 「ステートレスなプロトコルの上で、ステートフルな挙動をグローバルに持ち込んでしまった」 状態だよ。

君が書いたそのコード、具体的にどんな「地獄」が待っているか、3つのポイントで解説するね。


1. ユーザー間で「情報が混ざる」という致命的なセキュリティ事故

これが一番ヤバい。Server Actionsのモジュールスコープ(関数の外側)で定義された変数は、そのサーバープロセスが生きている間、すべてのリクエスト(すべてのユーザー)で共有されるんだ。

  • 何が起きるか: 1. Aさんがログインして getUser(AのID) を実行する。userInfo にAさんの秘密の情報が保存される。

    1. Bさんがログインして getUser(BのID) を実行する。

    2. サーバー側では userInfo が既に存在するので、Bさんの画面にAさんのプロフィールが表示される。

  • 結論: 自分の住所やクレカ情報が、赤の他人に丸見えになる。「個人情報漏洩」確定のバグだよ。

2. インスタンスごとに「記憶」が違う(非決定的な挙動)

「じゃあ、自分一人で使ってる個人開発ならいいのか?」と思うかもしれないけど、デプロイ先が Vercel や AWS Lambda などのサーバーレス環境だと、さらに挙動がカオスになるよ。

  • 何が起きるか:

    サーバーレス環境では、アクセスが増えると「インスタンス(コンテナ)」が複数立ち上がるんだ。

    • インスタンス1:Aさんの情報をキャッシュしている。

    • インスタンス2:まだ誰もアクセスしていない(キャッシュなし)。

  • 挙動: ブラウザをリロードするたびに、どのインスタンスに繋がるかは運次第。さっきまでキャッシュが効いていたのに、次の瞬間にはまたDBを見に行く……という、再現性のない「気まぐれな挙動」 に悩まされることになるんだ。

3. メモリリーク(サーバーの窒息)

もし userInfo が単なるオブジェクトじゃなくて、大量のデータを含むリストだった場合、その変数はプロセスが死ぬまでメモリを食い続けるよ。

  • 何が起きるか: リクエストが終わっても、ガベージコレクション(GC)はこの userInfo を解放できない(グローバルに参照が残っているからね)。アクセスが来るたびにメモリ使用量が増えていき、最終的には Out of Memory (OOM) でサーバーがクラッシュする。

🛠️ じゃあ、どうすれば「正しく」キャッシュできるの?

Next.js(Server Side)でデータをキャッシュしたいなら、グローバル変数ではなく、Next.jsが用意した専用のキャッシュ機構を使うのが鉄則だよ!

A. 同一リクエスト内でのキャッシュ(Request Memoization)

同じページ内で何度も同じDB呼び出しをするなら、Reactの cache を使う。これは1回のリクエストが終われば消えるから、他人の情報が混ざる心配はないよ。

TypeScript

import { cache } from 'react'

export const getUser = cache(async (userId: number) => {
  return await db.user.findUnique({ where: { id: userId } })
})

B. 永続的なキャッシュ(Data Cache)

DBの結果を一定時間サーバーに保存したいなら、unstable_cache を使う。ただし、ここでは 「全ユーザー共通のデータ」なのか「ユーザー固有のデータ」なのか を厳密に区別する必要があるね。


⚠️ 見落とされがちな落とし穴:マルチスレッドと競合

JavaやC#を学んでいる君ならピンとくるかもしれないけど、JavaScriptはシングルスレッドでも、Node.jsのランタイムレベルで見れば非同期処理が並行して走っている。

もしこのグローバル変数を同時に書き換えようとしたら、いわゆる 「レースコンディション(競合状態)」 が起きて、データが壊れる可能性もあるんだ。

結論

"use server" の外側の変数は、「全ユーザー共通の、読み取り専用の定数」 以外には絶対に使っちゃダメだぜ!

個別の情報は、必ず「関数の引数」や「認証セッション」をベースに、リクエストごとに閉じたスコープで扱うのが、安全なシステム設計への第一歩だよ。

次は、この「キャッシュの地獄」を回避するための 「Next.js 15以降の最新のキャッシュ戦略」 について話してみる?それとも、「JavaScriptのクロージャを使った、より安全なメモリ管理」 について深掘りしてみるかな!