本文へ移動
verified onnext@16.3.3react@19
最終検証日 2026-08-27
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 で渡したときに出るエラーです。

#何が起きているか

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

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

Server Component  ──  値だけが越える  ──▶  Client Component
                        ✅ 文字列・数値・配列・オブジェクト
                        ✅ 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} でも、関数なら越えられません。

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

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

#最短の確認手順

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

#直し方

推奨:ハンドラを 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