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

Event handlers cannot be passed の直し方

実際に出るエラー

Error: Event handlers cannot be passed to Client Component props.
  {items: [...], onAdd: function f}
                        ^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.

Server Component から Client Component へ関数を props で渡したときに出るエラーです。

何が起きているか

関数は境界を越えられない。値は越えられる
Server Component
page.tsx
onAdd を定義した関数
Client Component
List.tsx(use client)
onClick で呼びたい

越えられる: items(配列)

越えられる: title(文字列)

越えられない: onAdd(関数)

App Router のコンポーネントは、既定で Server Component です。Server Component が返した内容は、そのままブラウザに届くわけではありません。いったん値だけの形に変換されてネットワークを越え、ブラウザ側で組み立て直されます。

関数はこの変換ができません。関数の中身は「実行できる形」であって、値ではないからです。だから境界で止められます。

境界を越えられるのは値だけ
Server Component
page.tsx
onAdd を定義した関数
Client Component
List.tsx(use client)

越えられる: 文字列・数値・配列・オブジェクト

越えられる: Date・Map・Set・BigInt

越えられない: 関数(このエラーの原因)

越えられない: クラスのインスタンス

原因はほぼこの3つ

1. onClick や onChange を Server Component から渡している

いちばん多い形です。

// app/cart/page.tsx(Server Component)
export default async function Page() {
  const items = await db.item.findMany()
  return <List items={items} onAdd={add} />   // ← ここ
}

2. コールバックを props 名で渡している

onXxx という名前でなくても、関数なら越えられません。ただし文言が変わります。formatter={fn} や render={fn} のように on で始まらない名前だと、Functions cannot be passed directly to Client Components と出ます(Functions cannot be passed directly の直し方)。

3. ライブラリのコンポーネントに関数を渡している

自分では境界を意識していなくても、そのライブラリが Client Component であれば同じことが起きます。

最短の確認手順

  1. エラー文の ^^^^^^^^^^ が指している props 名を見る
  2. その props を渡している側のファイルを開く
  3. そのファイルの先頭に 'use client' があるか確認する
  4. 無ければ、そこが Server Component です。渡している値が関数なら、それが原因です

直し方

押したときの動作は、受け取る側に置く
うまくいかない
// page.tsx(Server Component)
export default function Page() {
  return <List onAdd={() => add()} />
}

関数を渡そうとしている。ビルドで落ちる。

こう書く
// List.tsx
'use client'
 
export function List({ items }) {
  return <button onClick={() => add()}>追加</button>
}

動作は List の中に書く。渡すのは値だけ。

推奨:ハンドラを Client Component の内側で定義する

境界を「ブラウザの操作が必要な部品」まで下ろします。

// app/cart/page.tsx(Server Component のまま)
export default async function Page() {
  const items = await db.item.findMany()
  return <List items={items} />
}
// components/List.tsx
'use client'
 
export function List({ items }: { items: Item[] }) {
  const add = (id: number) => { /* ブラウザ側の処理 */ }
  return items.map((i) => <button key={i.id} onClick={() => add(i.id)}>追加</button>)
}

Page は Server Component のままなので、DB に直接アクセスできますし、商品データを扱うコードはブラウザに送られません。

サーバー側で処理したい場合:Server Function を渡す

「押したらサーバーで何かする」なら、Server Function は props として渡せます。関数そのものではなく参照だけが渡り、呼ぶとサーバー側で実行されるためです。

// app/cart/actions.ts
'use server'
 
export async function addToCart(id: number) {
  // サーバー側で実行される
}

やってはいけない直し方:Page に use client を付ける

エラーは消えます。境界を越えなくなるからです。ですが、

  • 商品データを取る処理まで含めてブラウザ側に置かれる
  • DB への直接アクセスが成立しなくなる
  • ページ全体の JavaScript がブラウザへ送られる

ので、解決にはなっていません。エラーが消えることと、正しく直ったことは違います。

なぜ開発中に気づきにくいのか

このエラーは描画のときに出ます。そのページが静的に prerender される構成なら、その描画はビルド中に走るので、ビルドが失敗して気づけます。

一方、リクエストごとに描画される構成だと、そのページを実際に開くまで出ません。開発中に触っていない画面だと、本番で初めて表面化することがあります。

再発防止

  • 境界は葉に近い側へ引く。ページ全体に use client を付けない
  • Server Component から渡す props は「値」だけ、と決めておく
  • ライブラリのコンポーネントを使うときは、それが Client Component かどうかを先に確認する

関連するエラー

クラスのインスタンスを渡した場合は、別の文言になります。

Only plain objects, and a few built-ins, can be passed to Client Components
from Server Components. Classes or null prototypes are not supported.

原因は同じ「境界を越えられないものを渡した」で、越えられないものの種類が違うだけです。

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

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

このコードはそのまま動きますか。動かない場合、どこを直すのが適切ですか。

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