0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】 `Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".`の解消

0
Last updated at Posted at 2026-09-15

登録処理、削除処理実装のタイミングで同じエラーに引っかかったので備忘録として残しておきます。

要約

  • Server Component から Client Component へ「通常の関数」は渡せない
  • 解決策: Props で関数を渡すのをやめ、Client Component 側で "use server" 付きの関数(Server Action)を直接インポートする。

エラー内容

親(Server Component)で関数を作成し、Props 経由で子(Client Component)へ渡そうとしたら以下のエラーが発生した。

エラーメッセージ:
Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

// page.tsx (Server Component)
import TransactionDetail from "./components/TransactionDetail";
import { deleteTransaction } from "./actions/deleteTransaction";

export default async function Page({ params }) {
  // 親側で関数をラップして渡そうとすると、境界線を越えられずエラーになる
  const handleClickDelete = async () => {
    await deleteTransaction(params.id);
  };

  return <TransactionDetail handleClickDelete="{handleClickDelete}"/>;
}


修正後

親からはデータ(IDなど)だけを渡し、Server Action("use server")は Client Component 側で直接インポートします。

1. Server Action(actions/deleteTransaction.ts)

先頭に "use server" を付与します。これがないとブラウザ側から呼び出せません。

"use server";

import { getRepository } from "@/utils/data-source";
import { Transaction } from "@/entities/Transaction";

export async function deleteTransaction(id: string) {
  const repo = await getRepository(Transaction);
  await repo.softDelete(id);
}

補足: "use server" を付け忘れるとどうなるか?

Client Component からインポートしている deleteTransaction.ts の "use server" を消す(つけ忘れる)と、以下のようなエラーが発生します。

Module not found: Can't resolve 'fs'

これは、Node.js 専用のモジュール(DB接続ライブラリ等)がブラウザ環境用コードとして読み込まれてしまうために起こります。
Client Component から呼び出すサーバー処理には、必ず "use server" が必須です。

2. 親コンポーネント(page.tsx / Server Component)

関数は渡さず、必要な ID のみを Props で渡します。

// page.tsx
import TransactionDetail from "./components/TransactionDetail";

export default async function Page({ params }) {
  const { transactionId } = await params;
  return <TransactionDetail transactionId="{transactionId}"/>;
}

3. 子コンポーネント(TransactionDetail.tsx / Client Component)

Server Action を直接インポートして実行します。

// TransactionDetail.tsx
"use client";

import { useRouter } from "next/navigation";
import { deleteTransaction } from "../actions/deleteTransaction";

export default function TransactionDetail({ transactionId }: { transactionId: string }) {
  const router = useRouter();

  const handleDelete = async () => {
    try {
      // Client Componentから直接Server Actionを呼び出す
      await deleteTransaction(transactionId);
      router.push("/dashboard");
    } catch (e) {
      alert("削除失敗");
    }
  };

  return <button onClick={handleDelete}>削除</button>;
}

まとめ

Next.js (App Router) では、サーバーとクライアントの「境界線(Network Boundary)」を正しく意識することが重要です。

1. サーバーからクライアントへ関数を渡してはいけない

  • NG: Server Component から Client Component へ、Props で関数を渡す。
  • 理由: JavaScript の関数オブジェクトは境界線を越えてシリアライズ(データ化)できないため、Next.js で実行時エラーになります。

2. データだけを渡し、Server Action は子側で直接インポートする

  • OK: 親(Server Component)からは ID などの「値」だけを渡し、Client Component 側で "use server" が付いた関数を直接 import して呼び出す。
  • 理由: "use server" を宣言した関数であれば、Client Component から直接インポートしても Next.js が自動的に通信用 API を生成し、ブラウザとサーバー間を安全に橋渡ししてくれるためです。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?