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さんの秘密の情報が保存される。-
Bさんがログインして
getUser(BのID)を実行する。 -
サーバー側では
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のクロージャを使った、より安全なメモリ管理」 について深掘りしてみるかな!