Skip to content

Next.jsで関数をクライアントコンポーネントに渡す方法

問題の説明

Next.js(バージョン13以降)で、サーバーコンポーネントからクライアントコンポーネントに関数をプロップとして渡そうとすると、次のエラーが発生します:

tsx
Error: Functions cannot be passed directly to Client Components
unless you explicitly expose it by marking it with "use server"

このエラーは、サーバーサイドで実行される関数をクライアントコンポーネントに直接渡そうとしたときに発生します。具体的には次のような状況でよく見られます:

  • サーバーコンポーネントで定義された関数をクライアントコンポーネントに渡す
  • error.tsxファイルをクライアントコンポーネントとして明示していない
  • サーバーアクションを使用せずにイベントハンドラーを渡す
  • 動的ルート内のコンポーネントでクライアントディレクティブが不足

例示されたコードでは、renderCellプロパティに関数を渡そうとしています:

tsx
matchesColumns.push({
  field: 'pk',
  headerName: "",
  flex: 0.3,
  renderCell: (params, event) => ( // 関数が直接渡されている
    <Link href={`/matches/${encodeURIComponent(params.row.pk)}`}>
      Details
    </Link>
  )
});

根本原因

Next.jsのアーキテクチャでは:

  1. サーバーコンポーネント - サーバーサイドでのみ実行(デフォルト)
  2. クライアントコンポーネント - クライアントサイドで実行('use client'必須)

サーバーコンポーネントで定義された関数をクライアントコンポーネントに直接渡すと、セキュリティリスクやパフォーマンス問題が発生する可能性があります。Next.jsはこれを防止するために、明示的なマーキング('use server')を要求します。

主な解決策

方法1: クライアントコンポーネントの明示

コンポーネントの先頭に'use client'ディレクティブを追加します。特に動的ルート内のコンポーネントで必要です:

tsx
'use client'; // クライアントコンポーネントとして明示

export default function SingleProduct({ props }) {
  // コンポーネント実装
}

重要なポイント

  • 動的ルート内のコンポーネント/app/[id]/page.tsx)でクライアントコンポーネントを使用する場合
  • データ取得をサーバーで行い、表示のみをクライアントで行う分割アーキテクチャに適している

方法2: サーバーアクションの使用

関数をクライアントに公開するには'use server'でマークします:

tsx
export default async function Page() {
  // サーバーアクションの定義
  const deleteItem = async (itemId: string) => {
    'use server'; // サーバーアクションとして明示
    
    // 削除ロジック実装
    console.log(`Deleting item: ${itemId}`);
    return { success: true };
  };

  return <ClientComponent deleteItem={deleteItem} />;
}
tsx
'use client';

export function ClientComponent({ deleteItem }) {
  return (
    <button onClick={async () => {
      await deleteItem("item123");
      alert("削除完了");
    }}>
      アイテムを削除
    </button>
  );
}

サーバーアクションの特徴

  1. サーバーサイドでのみ実行される
  2. クライアントから安全に呼び出し可能
  3. Next.js 13.4以降で安定版として利用可能

方法3: エラーファイル(error.tsx)の修正

app/error.tsxファイルを使用している場合、明示的にクライアントコンポーネントとしてマークする必要があります:

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'

よくある間違いと修正例

間違った実装:

tsx
export default async function Home() {
  const handleSubmit = (data) => {
    console.log(data); // サーバー関数
  };

  return <Form onSubmit={handleSubmit} />;
}

正しい実装:

tsx
export default async function Home() {
  // サーバーアクションとして定義
  const handleSubmit = async (data) => {
    'use server'; // 明示的にマーク
    console.log(data);
  };

  return <Form onSubmit={handleSubmit} />;
}
tsx
'use client'; // クライアントコンポーネントとして明示

export default function Form({ onSubmit }) {
  // フォーム実装
}

パフォーマンスベストプラクティス

  1. コンポーネントの責務分離

    • データ取得: サーバーコンポーネント
    • インタラクション処理: クライアントコンポーネント
  2. 不要な関数の受け渡しを避ける

    tsx
    // 非推奨
    <ClientComponent handler={() => {...}} />
    
    // 推奨: 必要なデータのみ渡す
    <ClientComponent data={processedData} />
  3. コード分割パターン

    tsx
    async function ServerComponent() {
      const data = await fetchData();
      
      return (
        <div>
          <ClientComponent data={data} />
        </div>
      )
    }

まとめ

Next.jsアプリケーションで関数をクライアントコンポーネントに渡す際は、以下のいずれかの方法を使用します:

  1. 'use client'ディレクティブでクライアントコンポーネントを明示
  2. サーバーサイド関数に'use server'をマークしてサーバーアクションとして公開
  3. error.tsxなどの特別なファイルを必ずクライアントコンポーネント化

これらの手法を適切に適用することで、サーバーとクライアント間のデータフローを安全かつ効率的に管理でき、新しいApp Routerアーキテクチャの利点を最大限に活用できます。

Next.jsバージョン情報

  • 本記事の内容は Next.js v13.4 以降を対象としています
  • サーバーアクションはnext.config.jsで有効化が必要な場合があります