Next.jsで関数をクライアントコンポーネントに渡す方法
問題の説明
Next.js(バージョン13以降)で、サーバーコンポーネントからクライアントコンポーネントに関数をプロップとして渡そうとすると、次のエラーが発生します:
Error: Functions cannot be passed directly to Client Components
unless you explicitly expose it by marking it with "use server"
このエラーは、サーバーサイドで実行される関数をクライアントコンポーネントに直接渡そうとしたときに発生します。具体的には次のような状況でよく見られます:
- サーバーコンポーネントで定義された関数をクライアントコンポーネントに渡す
error.tsx
ファイルをクライアントコンポーネントとして明示していない- サーバーアクションを使用せずにイベントハンドラーを渡す
- 動的ルート内のコンポーネントでクライアントディレクティブが不足
例示されたコードでは、renderCell
プロパティに関数を渡そうとしています:
matchesColumns.push({
field: 'pk',
headerName: "",
flex: 0.3,
renderCell: (params, event) => ( // 関数が直接渡されている
<Link href={`/matches/${encodeURIComponent(params.row.pk)}`}>
Details
</Link>
)
});
根本原因
Next.jsのアーキテクチャでは:
- サーバーコンポーネント - サーバーサイドでのみ実行(デフォルト)
- クライアントコンポーネント - クライアントサイドで実行(
'use client'
必須)
サーバーコンポーネントで定義された関数をクライアントコンポーネントに直接渡すと、セキュリティリスクやパフォーマンス問題が発生する可能性があります。Next.jsはこれを防止するために、明示的なマーキング('use server'
)を要求します。
主な解決策
方法1: クライアントコンポーネントの明示
コンポーネントの先頭に'use client'
ディレクティブを追加します。特に動的ルート内のコンポーネントで必要です:
'use client'; // クライアントコンポーネントとして明示
export default function SingleProduct({ props }) {
// コンポーネント実装
}
重要なポイント
- 動的ルート内のコンポーネント(
/app/[id]/page.tsx
)でクライアントコンポーネントを使用する場合 - データ取得をサーバーで行い、表示のみをクライアントで行う分割アーキテクチャに適している
方法2: サーバーアクションの使用
関数をクライアントに公開するには'use server'
でマークします:
export default async function Page() {
// サーバーアクションの定義
const deleteItem = async (itemId: string) => {
'use server'; // サーバーアクションとして明示
// 削除ロジック実装
console.log(`Deleting item: ${itemId}`);
return { success: true };
};
return <ClientComponent deleteItem={deleteItem} />;
}
'use client';
export function ClientComponent({ deleteItem }) {
return (
<button onClick={async () => {
await deleteItem("item123");
alert("削除完了");
}}>
アイテムを削除
</button>
);
}
サーバーアクションの特徴
- サーバーサイドでのみ実行される
- クライアントから安全に呼び出し可能
- Next.js 13.4以降で安定版として利用可能
方法3: エラーファイル(error.tsx)の修正
app/error.tsx
ファイルを使用している場合、明示的にクライアントコンポーネントとしてマークする必要があります:
'use client'; // 必須
const Error = ({ error, reset }: {
error: Error;
reset: () => void;
}) => {
return (
<div className="error-container">
<h2>エラーが発生しました</h2>
<button onClick={() => reset()}>再試行</button>
</div>
)
}
export default Error;
注意点
- Next.jsのエラーハンドリングファイルは全てクライアントコンポーネントとして実装する必要があります
'use client'
がない場合、このエラーが頻発します
ケース別対応表
状況 | 解決策 | コード例 |
---|---|---|
カスタムレンダリング関数 | クライアントコンポーネント化 | 'use client' |
フォーム送信処理 | サーバーアクションを使用 | 'use server' |
データフェッチ後の処理 | クライアント側で処理実装 | イベントハンドラ内で実行 |
エラーページ実装 | error.tsxをクライアント化 | 'use client' |
よくある間違いと修正例
間違った実装:
export default async function Home() {
const handleSubmit = (data) => {
console.log(data); // サーバー関数
};
return <Form onSubmit={handleSubmit} />;
}
正しい実装:
export default async function Home() {
// サーバーアクションとして定義
const handleSubmit = async (data) => {
'use server'; // 明示的にマーク
console.log(data);
};
return <Form onSubmit={handleSubmit} />;
}
'use client'; // クライアントコンポーネントとして明示
export default function Form({ onSubmit }) {
// フォーム実装
}
パフォーマンスベストプラクティス
コンポーネントの責務分離
- データ取得: サーバーコンポーネント
- インタラクション処理: クライアントコンポーネント
不要な関数の受け渡しを避ける
tsx// 非推奨 <ClientComponent handler={() => {...}} /> // 推奨: 必要なデータのみ渡す <ClientComponent data={processedData} />
コード分割パターン
tsxasync function ServerComponent() { const data = await fetchData(); return ( <div> <ClientComponent data={data} /> </div> ) }
まとめ
Next.jsアプリケーションで関数をクライアントコンポーネントに渡す際は、以下のいずれかの方法を使用します:
'use client'
ディレクティブでクライアントコンポーネントを明示- サーバーサイド関数に
'use server'
をマークしてサーバーアクションとして公開 error.tsx
などの特別なファイルを必ずクライアントコンポーネント化
これらの手法を適切に適用することで、サーバーとクライアント間のデータフローを安全かつ効率的に管理でき、新しいApp Routerアーキテクチャの利点を最大限に活用できます。
Next.jsバージョン情報
- 本記事の内容は Next.js v13.4 以降を対象としています
- サーバーアクションは
next.config.js
で有効化が必要な場合があります