こんにちは!Reactの学習、毎日お疲れ様です。
コンポーネントを組み合わせて画面を作っていく楽しさが分かってきた一方で、「なんだか最近、エラーがよく出るな……」と頭を抱えていませんか?
特に、今熱い注目を集めている「React Server Components(RSC)」を触り始めると、「あれ? さっきまで動いていたコードなのに、突然怒られたぞ…?」という壁にぶつかる人が続出しています。
大丈夫ですよ、あなたが躓いているのはごく自然なことです。今日は、そのエラーの正体である「Propsのシリアライズ制約」について、難しい専門用語をできるだけ取っ払って、お買い物の例え話をしながら優しくほどいていきましょう。
—
1. サーバーとクライアントの「宅配便ルール」を知ろう
React Server Components(RSC)の大きな特徴は、「画面を作る作業の大部分を、手元のパソコン(クライアント)じゃなくて、遠くのサーバーにお任せしちゃおう!」という仕組みです。
ここで、サーバー(遠くの工場)から、私たちのブラウザ(手元のお家)へデータを送る場面を想像してみてください。
工場で作ったピカピカの家具を、お家に届けるとき、どうやって送りますか?
もちろん、ダンボール箱に分解して梱包し、トラックの荷台に載せて「宅配便」で送りますよね。
ここで重要なルールがあります。
「宅配便のトラックに乗せられるのは、形があるデータだけ」なんです。
- ダンボールに入れられるもの(送れるもの): 文字列、数字、真偽値(true/false)、シンプルな配列やオブジェクト
- ダンボールに入れられないもの(送れないもの): 生き物、燃え盛る炎、複雑な機械(関数やクラスのインスタンス)
React Server Componentsの世界でもこれと全く同じことが起きています。サーバー側で作ったコンポーネントのデータを、ブラウザ側へPropsとして渡すとき、データは一度「文字列(JSON)」というダンボール箱に詰め替えられます。この箱詰め作業のことを「シリアライズ(直列化)」と呼びます。
—
2. なぜ「関数」や「クラス」は送れないの?
「ねえ、サーバーからクライアントへ、クリックされたときに動く関数をPropsでそのまま渡したいんだけど……」
初学者の頃、誰もが一度はこの発想に行き着きます。でも、これをやるとReactは真っ青になってエラーを吐き出します。なぜでしょうか?
先ほどの「宅配便」の例えに戻りましょう。
あなたが「このボタンを押すとピザが焼けるという仕組み(関数)」そのものをダンボールに詰めようとしたとします。でも、関数の中身はコードのロジック(プログラムの実行手順)です。さらに言えば、サーバーのメモリ上で動いている状態のものは、ブラウザの環境ではそのまま再現できません。
クラスのインスタンスも同じです。中に独自のメソッド(関数)や複雑な状態を持ったオブジェクトは、ダンボールに詰める(シリアライズする)ことができないのです。
—
3. つまずきがちな「やってはいけない例」と「その理由」
実際に、よくある「やっちゃった!」コードを見てみましょう。
// 【サーバーコンポーネント】
import ClientButton from ‘./ClientButton’;
export default function ServerPage() {
// サーバー側で定義した関数
const handleClick = () => {
console.log(‘サーバーの関数が呼ばれたよ!’);
};
return (
サーバーとクライアントの実験
{/ ⚠️ サーバーからクライアントへ関数を直接渡そうとしてエラーになるパターン! /}
);
}
このコードをRSCの環境で動かすと、「関数はシリアライズできません(Functions cannot be passed directly to Client Components)」というエラーが出ます。
初めてこれを見たときは「えっ、親から子にイベントハンドラーを渡すのってReactの基本じゃないの!?」とパニックになりますよね。でも大丈夫、これはRSCならではの「サーバーとブラウザの壁」があるからこその制限なんです。
—
4. 大丈夫!こうすれば回避できる「2つの解決策」
この壁を乗り越えるためには、ちょっとした発想の転換が必要です。代表的な2つのアプローチを見ていきましょう。
解決策A:イベントはクライアント側で完結させる(王道パターン)
クリックされたときに「なにをしたいか」という具体的な関数(ロジック)自体をサーバーから送るのではなく、「ブラウザ側(クライアントコンポーネント)で関数を用意する」のが一番シンプルな解決策です。
// 【クライアントコンポーネント】
‘use client’; // ここから先はブラウザで動くよという合図
export default function ClientButton() {
// ブラウザ側で関数を定義するから、シリアライズの心配なし!
const handleClick = () => {
alert(‘ボタンが押されました!(クライアント側で処理)’);
};
return (
);
}
解決策B:サーバーのデータが必要なら「Server Actions」を使う
「どうしてもサーバー側の処理(データベースの書き換えなど)を呼び出したいんだ!」という場合は、Reactの最新機能であるServer Actions(サーバーアクション)を使います。
関数そのものを適当に渡すのではなく、`’use server’`という魔法の合言葉を使って、安全にサーバーの関数を呼び出す専用のパイプラインを作ってあげるイメージです。
// 【サーバーアクションを定義したファイル、またはサーバーコンポーネント内】
async function saveDataToDatabase() {
‘use server’; // これが「安全な通信用パイプライン」の合図!
// ここでデータベースに保存する処理などを書く(サーバー側で実行される)
console.log(‘データベースに保存しました!’);
}
// 【クライアントコンポーネント】
‘use client’;
export default function ClientForm({ action }: { action: () => Promise
return (
);
}
これなら、ダンボールに不可能なものを詰め込もうとして怒られることもありません。仕組みさえ分かってしまえば、怖くありませんよね!
—
まとめ
- React Server Componentsでは、サーバーからクライアントへ「関数」や「クラス」をPropsとして直接渡すことはできない。(データを「ダンボール(JSON)」に詰めて送るルールがあるため)
- エラーが出たら、「あ、今ブラウザとサーバーの壁を越えようとして変なものを送っちゃったんだな」と優しく思い出してあげてください。
- 解決するには、イベント処理をクライアント側に持たせるか、Server Actions等の正しい仕組みを使いましょう。
フロントエンドの技術は日々進化していて、最初は戸惑うこともたくさんありますが、一つひとつのルールには「ちゃんと理由」があります。焦らず、ご自身のペースで一歩ずつ進んでいきましょうね。あなたのコーディングライフを、心から応援しています!

コメント