本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

Server から Client へ渡せる props の値

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

なぜ制限があるのか

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

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

一覧

Client Component へ渡せる値・渡せない値
渡すもの越えられるか備考
文字列 / 数値 / 真偽値 / nullできるそのまま
配列 / プレーンなオブジェクトできる中身も再帰的に見られる
undefinedできるキーは消えない
Date / Map / SetできるJSON とは違い、型のまま渡る
Promiseできる受け取る側が use() で待つ
Symbol.for(...)できる登録済みのものだけ
関数できない例外は use server を書いたものだけ
クラスのインスタンスできない入れ子でも弾かれる
Symbol('x')できない登録されていないもの
渡せる渡せない
文字列・数値・真偽値・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 問