Next.js 16.3.1 でこのエラーを実際に出して、なぜ「'use client' を付けたのに直らない」が起きるのか を確かめました。
検証環境は Next.js 16.3.1 / React 19.2.8 / Node.js v22.22.2 です。
対象読者: 子に 'use client' を付けているのにこのエラーが出て、原因が分からない方。
再現コード
子は 'use client' 付き のクライアントコンポーネントです。
// app/d/button.tsx
'use client'
export function Button({ onClick, label }: { onClick: () => void; label: string }) {
return <button onClick={onClick}>{label}</button>
}
親はサーバーコンポーネント('use client' なし)で、その子に関数を渡します。
// app/d/page.tsx
import { Button } from './button'
export default function Page() {
return <Button label="押す" onClick={() => console.log('clicked')} />
}
出るエラー
✓ Compiled successfully in 549ms
Running TypeScript ...
Finished TypeScript in 1806ms ...
Collecting page data using 3 workers ...
Generating static pages using 3 workers (0/3) ...
Error occurred prerendering page "/d". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Event handlers cannot be passed to Client Component props.
{label: "押す", onClick: function onClick}
^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
at ignore-listed frames {
digest: '1022984954'
}
Export encountered an error on /d/page: /d, exiting the build.
見落としやすい 3 点
1. コンパイルも型チェックも通っている
✓ Compiled successfully in 549ms
Finished TypeScript in 1806ms ...
型エラーではありません。 onClick: () => void に関数を渡しているので、TypeScript から見れば完全に正しいコードです。落ちるのは、その後の プリレンダリング(Generating static pages) の工程です。
「型が通っているのだからコードは合っているはず」という前提でエラーを読むと、原因にたどり着けません。
2. 'use client' は 子には既に付いている
このエラーで検索すると「クライアントコンポーネントにしましょう」という説明が出てきますが、上のコードでは子にもう付いています。それでも落ちます。
問題は子ではなく、関数を作っている場所 です。() => console.log('clicked') を書いたのは page.tsx、つまり サーバー側 です。
3. エラーの ^^^^ は渡された props を指している
{label: "押す", onClick: function onClick}
^^^^^^^^^^^^^^^^
label の "押す" には下線がなく、onClick の関数だけに付いています。どの prop が問題かを name 単位で名指ししてくれています。 props が多いコンポーネントではこれが決め手になります。
なぜ関数だけ渡せないのか
サーバーコンポーネントの結果は、一度 データに書き起こされて ブラウザへ送られます。文字列・数値・配列・オブジェクトは書き起こせますが、関数は書き起こせません。
() => console.log('clicked') という関数の中身をブラウザに送るには、その関数が閉じ込めている変数やスコープごと持っていく必要があり、それは「送る」という操作の定義から外れています。だから境界を越えられません。
同じ理由で、次のものも渡せません。
- 関数(イベントハンドラ全般)
- クラスのインスタンス
Symbol
渡せるのは 「文字に書き起こせる値」だけ と覚えるのが実用的です。
直し方は 2 つ
A. 関数を、それを使う側(クライアント側)で作る
いちばん素直な直し方です。onClick の中身をクライアントコンポーネント側に閉じ込めます。
// app/d/button.tsx
'use client'
export function Button({ label }: { label: string }) {
return <button onClick={() => console.log('clicked')}>{label}</button>
}
// app/d/page.tsx(サーバーのまま)
import { Button } from './button'
export default function Page() {
return <Button label="押す" />
}
サーバーからは 文字列だけ を渡します。境界を越えるのがデータだけになるので通ります。
B. サーバーで実行したい処理なら、Server Actions を使う
「ボタンを押したらサーバー側で保存したい」なら、関数を渡すのではなく Server Actions の出番です。
// app/d/actions.ts
'use server'
export async function save(formData: FormData) {
// サーバーで実行される
}
// app/d/page.tsx(サーバーのまま)
import { save } from './actions'
export default function Page() {
return (
<form action={save}>
<button type="submit">保存する</button>
</form>
)
}
'use server' を付けた関数は、関数の中身ではなく「呼び出すための ID」がブラウザに送られる 仕組みなので、境界を越えられます。「関数を渡したい」の多くは、実はこちらが求めていた形です。
判断の順番
このエラーが出たときは、次の順で見てください。
-
^^^^が指している prop 名を確認する(どの関数が原因か) -
その関数を書いているファイルに
'use client'があるか を見る。なければサーバー側で作っている - その処理は ブラウザでやりたいのか、サーバーでやりたいのか を決める
- ブラウザ → 関数をクライアントコンポーネントの中に移す(A)
- サーバー → Server Actions にする(B)
「とりあえず 'use client' を足す」は、足す場所を間違えると直らないうえ、直った場合もページ全体がクライアント側に落ちて別の問題を呼びます。原因は「境界を越えて何を渡したか」であって、「どこに 'use client' が足りないか」ではありません。
関連
このエラーを含む Next.js 16 のエラー 12 件を、実機で再現した出力そのままで逆引きにまとめた章を無料公開しています。
サーバーとクライアントの境界そのものを腰を据えて理解したい場合は、同じ本の第 8 章が該当します。