本文へ移動
verified onnext@16.3.3react@19
最終検証日 2026-08-27
Next.js道場

Server から Client へ渡せる値

Server Component から Client Component へ渡せるのは値だけで、関数とクラスのインスタンスは渡せません。

#なぜ制限があるのか

Server Component はサーバーで実行され、Client Component はブラウザにも送られます。その境界を props が越えるとき、値はネットワークを通れる形に変換されます。

関数やクラスのインスタンスは、値だけを運ぶと別物になってしまいます。React はそういう「一部だけ壊れた値」を作らず、境界で止めます。

#一覧

渡せる渡せない
文字列・数値・真偽値・null関数
配列クラスのインスタンス
プレーンなオブジェクトSymbol('x')(登録されていないもの)
Dateプロトタイプが null のオブジェクト
Map Set
BigInt
Promise
Server Component の要素(children

#JSON を基準にしない

いちばん多い誤解がこれです。JSON.stringify で通るかどうかは基準になりません。

  • Date は JSON では文字列になるが、ここではそのまま渡る
  • Map は JSON では空オブジェクトになるが、ここではそのまま渡る

基準は「実行できる形を持っているか」です。関数とクラスのインスタンスだけが、それを持っています。

// 渡る。受け取る側で toISOString() も size も使える
<View at={new Date()} counts={new Map([['a', 1]])} />

#Symbol には例外がある

Symbol は渡せませんが、Symbol.for で作ったものは渡せます。

<Tag mark={Symbol('label')} />       // ❌ ビルドが落ちる
<Tag mark={Symbol.for('label')} />   // ✅ 通る
Only global symbols received from Symbol.for(...) can be passed to Client Components.

Symbol('label') は、同じ文字列で作っても毎回別物になります。向こう側で同じものを 作り直せないので運べません。Symbol.for はグローバルなレジストリに登録するので、 キーさえ運べば向こう側で同じものを取り出せます。

境界の規則は「値の種類」ではなく、向こう側で復元できるかで並んでいます。

#例外:Server Function は渡せる

関数は渡せない、と書きましたが、Server Function だけは例外です。

// app/actions.ts
'use server'

export async function addToCart(id: number) {
  // サーバー側で実行される
}
<AddButton onAdd={addToCart} />   // 渡せる

ただしこれは「関数が渡っている」のではありません。参照だけが渡り、呼ぶとサーバー側で実行されます。 中身のコードはブラウザに届きません。

#渡した props はすべてブラウザに届く

これが実務でいちばん重要な点です。

境界を越えた props は、画面に表示していなくてもブラウザに届きます。開発者ツールで読めます。

// 危険。ADMIN_KEY がブラウザに届く
return <Table rows={rows} apiKey={process.env.ADMIN_KEY} />
// 危険。row にパスワードのハッシュや内部フラグが含まれていれば、それも届く
const row = await db.user.findFirst()
return <Profile user={row} />

「使っていないから大丈夫」ではありません。渡した時点で届いています。

#実務での書き方

ORM が返した行をそのまま渡さず、必要な値だけを明示的に組み立てます。

const row = await db.user.findFirst()

const user = {
  id: row.id,
  name: row.name,
  joinedAt: row.joinedAt,   // Date はそのままでよい
}

return <Profile user={user} />

これは3つの問題を同時に解決します。

  1. クラスのインスタンスを渡してしまう事故を防ぐ
  2. 送りたくない列がブラウザに届く事故を防ぐ
  3. 送るデータ量が減る

#やってはいけない回避策

<Profile user={JSON.parse(JSON.stringify(row))} />

エラーは消えますが、

  • Date が文字列に変わり、受け取る側で壊れる
  • undefined のキーが消える
  • 送りたくない列は依然として送られる

ので、直したことになりません。必要な値を選ぶほうが短く、安全です。

#迷ったときの判断

  1. その値は実行できる形を持っているか(関数・クラス)→ 渡せない
  2. サーバーで処理させたいのか → Server Function にする
  3. 受け取る側が本当に Client である必要があるか → 無ければ境界を引き直す
  4. その値がブラウザに届いて困らないか → 困るなら渡さない

理解できたか試す

では、このコードはどうなりますか。

Server Component から Client Component へ props として渡せないものを、すべて選んでください。

この問題を解くこのテーマの問題 3