【RSC】React Server Componentsとは?導入方法と注意点

はい、承知いたしました。React Server Components (RSC) に関する約5000語の詳細な記事を作成します。


【RSC】React Server Componentsとは?導入方法と注意点

1. はじめに:React開発の新たな地平へ

Reactは、モダンなWebアプリケーション開発においてデファクトスタンダードとも言えるほど普及したJavaScriptライブラリです。コンポーネントベースの設計思想と宣言的なUI構築により、複雑なユーザーインターフェースの開発を効率化してきました。しかし、Reactを用いたシングルページアプリケーション(SPA)開発には、長年付きまとってきた課題が存在します。

代表的な課題として、以下のようなものが挙げられます。

  • 初期表示速度の遅延: アプリケーションのJavaScriptバンドルサイズが大きくなると、ブラウザがスクリプトをダウンロード、パース、実行し、最初のレンダリングを行うまでの時間が長くなります(Time To Interactive – TTI)。これはユーザー体験を損なう大きな要因となります。
  • 大量のJavaScriptコード: アプリケーションの機能が増えるにつれて、クライアントサイドで実行されるJavaScriptコードが増大します。これにより、バンドルサイズが肥大化し、ダウンロードと実行のコストが増加します。
  • データ取得の非効率性: 多くのSPAでは、クライアントサイドでコンポーネントがマウントされてからAPIコールを行います。ネストされたコンポーネントがそれぞれデータを取得する場合、いわゆる「N+1問題」や「データ取得のウォーターフォール」が発生し、レンダリング完了までに時間がかかります。
  • サーバーサイドレンダリング (SSR) や静的サイト生成 (SSG) の限界: 初期表示速度やSEOの課題を解決するためにSSRやSSGが導入されてきました。SSRはサーバーでHTMLを生成し、クライアントでハイドレーション(イベントハンドラーのアタッチなど)を行いますが、結局インタラクティブになるためにはクライアント側でJavaScriptを実行する必要があり、ハイドレーションのコストも無視できません。SSGはビルド時に静的なページを生成しますが、動的なコンテンツやユーザー固有のデータ表示には向いていません。
  • APIキーなどの機密情報の取り扱い: クライアントサイドでデータ取得を行う場合、APIキーなどの機密情報をクライアントコードに含めることはセキュリティリスクとなります。

これらの課題に対し、Reactチームは全く新しいアプローチを提案しました。それが「React Server Components (RSC)」です。RSCは、コンポーネントの一部または全体をサーバー上でレンダリングし、その結果をクライアントに送信することで、前述の課題を根本的に解決しようと試みる革新的な技術です。

本記事では、React Server Componentsがどのようなものか、なぜ必要とされているのか、その仕組み、具体的な導入方法(特にNext.js App Routerを例に)、そして開発における注意点や制約について、詳細かつ網羅的に解説します。この記事を読むことで、RSCの概念を理解し、ご自身のプロジェクトへの導入を検討するための知識を得られるでしょう。

2. React Server Components (RSC) とは

2.1. RSCの定義と目的

React Server Componentsは、その名の通りサーバー上でレンダリングされるReactコンポーネントです。従来のReactコンポーネントがブラウザ(クライアントサイド)で実行されるのに対し、RSCはサーバーサイドで実行されます。

RSCの主な目的は以下の通りです。

  • JavaScriptバンドルサイズの削減: サーバーでレンダリングされるため、RSC自体やRSCがインポートするモジュールはクライアントのJavaScriptバンドルに含まれません。これにより、ダウンロード、パース、実行のコストを大幅に削減できます。
  • 初期表示速度の向上: サーバーでデータを取得し、レンダリング結果を直接クライアントに送信できるため、クライアント側でのデータ取得や追加のレンダリングを待つ必要がありません。ストリーミングによる高速な応答も可能です。
  • データ取得の効率化: サーバー上でデータ取得を完結させることができます。クライアントコンポーネントがマウントされるのを待たずに、サーバーコンポーネント内で直接データベースや外部APIにアクセスし、データを取得できます。これにより、データ取得のウォーターフォールを防ぎ、パフォーマンスを向上させます。
  • 機密情報の安全な取り扱い: APIキーやデータベース接続情報などの機密情報をサーバーコンポーネント内に閉じることで、クライアントサイドに漏洩するリスクを排除できます。
  • サーバーリソースの活用: サーバーの計算リソースを活用して、コストの高い処理(マークダウンのパース、画像の変換など)をサーバーで行い、クライアントの負担を軽減できます。

2.2. 従来のReactコンポーネント(クライアントコンポーネント)との違い

RSCを理解する上で最も重要なのは、従来のReactコンポーネント、すなわち「クライアントコンポーネント」との違いを明確にすることです。

特徴 React Server Components (RSC) React Client Components (RCC)
実行環境 サーバー ブラウザ(クライアント)
ファイル拡張子 .server.js (またはフレームワークの規約) .client.js (またはフレームワークの規約)
デフォルト Next.js App Routerなどではデフォルト 従来のReact開発ではデフォルト
インタラクティビティ なし(イベントハンドラーを持てない) あり(イベントハンドラー、Hooks使用可)
状態管理 不可 (useState, useReducer など使用不可) 可 (useState, useReducer など使用可)
副作用 不可 (useEffect, useLayoutEffect など使用不可) 可 (useEffect, useLayoutEffect など使用可)
ブラウザAPI 使用不可 (window, document など) 使用可 (window, document など)
データ取得 サーバーサイドで直接データソースにアクセス クライアントサイドでAPIをコール
バンドルサイズ ゼロ (クライアントのJSバンドルに含まれない) クライアントのJSバンドルに含まれる
Props シリアライズ可能な値、または他のコンポーネント シリアライズ可能な値、関数、オブジェクトなど
Context API 使用に制約あり(特別な仕組みが必要) 標準的に使用可

この表から分かるように、RSCとクライアントコンポーネントは役割が全く異なります。

  • RSC: データ取得、機密性の高い処理、サーバーでの静的な(または静的に近い)マークアップ生成に適しています。インタラクティブな要素は持ちません。
  • クライアントコンポーネント: ユーザーとのインタラクション、状態管理、ブラウザAPIへのアクセス、副作用の実行など、クライアントサイドでの動的な振る舞いに必要です。従来のReactコンポーネントの役割を引き継ぎます。

実際のアプリケーションでは、これら2種類のコンポーネントを組み合わせて使用します。サーバーコンポーネントでページの基本的な構造とデータを取得し、インタラクティブな部分が必要な箇所だけクライアントコンポーネントを使用するというのが基本的なパターンです。

3. なぜRSCが必要なのか?従来のSPA、SSR、SSGとの比較

前述のように、RSCは従来のWeb開発手法が抱える課題を解決するために登場しました。ここでは、既存の手法と比較しながら、RSCの必要性をさらに掘り下げます。

3.1. 従来のSPAの課題(再確認)

現代の多くのSPAは、クライアントサイドで全てのレンダリングを行います(CSR – Client-Side Rendering)。

  1. ユーザーがURLにアクセス。
  2. サーバーは最低限のHTML(通常は<div id="root"></div>のようなプレースホルダーとJavaScriptへの参照)を返す。
  3. ブラウザはHTMLをパースし、JavaScriptファイルをダウンロードする。
  4. JavaScriptがパースされ、実行される。
  5. Reactが起動し、APIをコールしてデータを取得する。
  6. データが取得できたら、Reactが仮想DOMを構築し、DOMを更新してUIをレンダリングする。
  7. ハイドレーションが行われ、イベントハンドラーがアタッチされる。
  8. ようやくページがインタラクティブになる(TTI)。

このプロセスでは、JavaScriptのダウンロードと実行が完了するまで何も表示されないか、最低限のローディング表示しか行われません。また、アプリケーションが大規模になるほどJavaScriptバンドルが肥大化し、ダウンロード・実行時間がさらに増加します。さらに、APIコールのウォーターフォールもパフォーマンスのボトルネックとなります。

3.2. SSR (Server-Side Rendering) との違い

SSRは、初期表示速度とSEOを改善するために登場しました。

  1. ユーザーがURLにアクセス。
  2. サーバーはReactコンポーネントをサーバーサイドでレンダリングし、最初のHTMLを生成して返す。
  3. ブラウザはHTMLを表示するため、ユーザーはすぐにコンテンツを見ることができる(First Contentful Paint – FCP)。
  4. ブラウザはJavaScriptファイルをダウンロードし、パース、実行する。
  5. Reactがクライアントサイドで起動し、サーバーで生成されたHTMLと仮想DOMの状態を同期させる(ハイドレーション)。
  6. ハイドレーションが完了すると、ページがインタラクティブになる(TTI)。

SSRは初期表示速度を改善しますが、いくつかの課題があります。

  • ハイドレーションのコスト: クライアントサイドで再びReactを起動し、サーバーで生成されたDOM構造とクライアントサイドの仮想DOMを同期させるハイドレーション処理が必要です。この処理はコストが高く、完了するまでページは完全にインタラクティブになりません。
  • JavaScriptバンドルは必要: サーバーでHTMLを生成しても、インタラクティブな要素やクライアントサイドのロジックのために、依然として大量のJavaScriptをクライアントに送信する必要があります。
  • サーバー負荷: リクエストごとにサーバーでレンダリングを行うため、サーバーの負荷が高くなる可能性があります。
  • データ取得の課題: サーバーでデータ取得を行う必要がありますが、ハイドレーションに必要なデータはクライアントにも渡す必要があります。また、クライアントコンポーネントでの追加のデータ取得はウォーターフォールを引き起こす可能性があります。

3.3. SSG (Static Site Generation) との違い

SSGは、ビルド時に全てのページを静的なHTMLファイルとして生成します。

  1. ビルド時にデータ取得とレンダリングを行い、静的なHTMLファイルを生成。
  2. ユーザーがURLにアクセスすると、サーバーは生成済みのHTMLファイルを返す。
  3. ブラウザはHTMLを表示する。
  4. クライアントサイドのJavaScriptがロード・実行され、ハイドレーションが行われる(インタラクティブになる)。

SSGは最も高速な初期表示と低コストでの配信(CDN利用など)を実現できます。しかし、以下のような限界があります。

  • 動的なコンテンツへの不向き: ビルド時以降に更新されるデータや、ユーザーごとに異なるコンテンツを表示するページには向いていません。動的な部分を後からクライアントサイドでフェッチする必要があります。
  • 大規模サイトでのビルド時間: ページ数が多いサイトではビルドに時間がかかることがあります。

3.4. RSCが提供するメリット

RSCは、これらの手法の利点を組み合わせ、欠点を補うことを目指します。

  • ゼロバンドルサイズ: RSC自体はクライアントのJSバンドルに含まれないため、純粋なサーバーコンポーネントだけで構成される部分は、クライアントに送られるJSを一切増やしません。
  • サーバーでのデータ取得: データ取得はサーバー上で完結し、ネットワーク遅延を最小限に抑えつつ、ウォーターフォールを防ぎます。
  • ストリーミングレンダリング: サーバーはレンダリング結果をストリームとしてクライアントに送信できます。これにより、一部のコンテンツがレンダリングでき次第すぐにブラウザが表示を開始でき、体感的なパフォーマンスが向上します。SSRのようにハイドレーションを待つ必要がありません。
  • 選択的なハイドレーション: クライアントコンポーネントだけがハイドレーションの対象となります。ページ全体ではなく、インタラクティブな部分のみにコストがかかります。
  • クライアントとサーバーのシームレスな連携: 同じReactのコンポーネントモデルを使用しながら、サーバーとクライアントの境界を意識的に設計できます。

要するに、RSCはサーバーで実行するべきこと(データ取得、計算、静的なマークアップ)クライアントで実行するべきこと(インタラクション、状態管理)を、コンポーネント単位で明確に分離し、それぞれの強みを最大限に活かすための仕組みです。これにより、高速な初期表示、少ないJavaScriptバンドル、高い開発効率を両立させることが可能になります。

4. RSCの仕組み

RSCは、従来のWeb開発とは異なる新しいアーキテクチャを採用しています。その核となる仕組みを理解することは、RSCを効果的に使う上で不可欠です。

4.1. ファイル拡張子による区別(フレームワーク依存)

React本体はファイル拡張子による区別を推奨していましたが、現在主流のRSCをサポートするフレームワーク(特にNext.js App Router)では、より統合されたアプローチが取られています。

  • デフォルト: Next.js App Routerの app ディレクトリ内では、全てのコンポーネントはデフォルトでサーバーコンポーネントとして扱われます。
  • use client ディレクティブ: ファイルの先頭に 'use client'; と記述することで、そのコンポーネントとそのインポート先全てをクライアントコンポーネントとして明示的に指定します。
  • use server ディレクティブ: 関数(特にフォームのアクションなど)の先頭に 'use server'; と記述することで、その関数をサーバーアクションとして定義します。これはクライアントコンポーネントからサーバーサイドのコードを安全に呼び出すための仕組みです。

このファイルシステムベースのルーティングと規約により、開発者はコンポーネントがサーバーで実行されるかクライアントで実行されるかを明確に意識して開発を進めることになります。

4.2. コンポーネントツリーの構造

RSC環境では、アプリケーションのコンポーネントツリーはサーバーコンポーネントとクライアントコンポーネントが混在する形になります。

基本的なルールは以下の通りです。

  • サーバーコンポーネントはクライアントコンポーネントをレンダリングできる: サーバーコンポーネントのJSX内でクライアントコンポーネントを子要素やプロパティとして記述できます。サーバーはクライアントコンポーネントの参照をペイロードに含めてクライアントに送ります。
  • クライアントコンポーネントはサーバーコンポーネントをレンダリングできない: クライアントコンポーネントのJSX内で直接サーバーコンポーネントをレンダリングすることはできません。ただし、親のサーバーコンポーネントから子要素として渡されたサーバーコンポーネントは、クライアントコンポーネント内で表示できます。これは、クライアントコンポーネントが「ブラックボックス」として受け取ったサーバーコンポーネントのレンダリング結果(ペイロードの一部)を表示するだけであり、クライアント自身がサーバーコンポーネントを実行するわけではないためです。

この制限により、インタラクティブなクライアントコンポーネントがデータ取得やサーバーサイドの処理を行うサーバーコンポーネントに依存するという、自然な構造が促進されます。

4.3. RSCペイロード

サーバーコンポーネントがレンダリングされると、その結果はHTMLではなく、特別なRSCペイロードという形式でクライアントに送信されます。このペイロードは、Reactがクライアントサイドでコンポーネントツリーを再構築し、表示するために必要な全ての情報を含んでいます。

RSCペイロードは以下のような要素で構成されます(これは概念的な表現であり、実際の形式はバイナリに近いかもしれません)。

  • レンダリングされたJSX要素の構造
  • 各要素のプロパティ(シリアライズ可能な値)
  • クライアントコンポーネントへの参照(識別子)
  • クライアントコンポーネントに渡されるプロパティ(シリアライズ可能な値または子要素としての他のコンポーネント)
  • データ(サーバーコンポーネントが取得したデータなど)
  • サーバーアクションへの参照

このペイロードはストリーミングで送信されるため、サーバーはレンダリングが完了した部分から順次クライアントにデータを送ることができます。これにより、クライアントは全てのデータとレンダリング結果が揃うのを待つことなく、受信したペイロードから段階的にUIをレンダリングできます。

4.4. Reactクライアントランタイムの役割

クライアントサイドでは、特別なReactクライアントランタイムが動作します。このランタイムは以下の役割を担います。

  1. RSCペイロードの受信: サーバーからストリームとして送信されるRSCペイロードを受信します。
  2. ペイロードの解析: 受信したペイロードを解析し、コンポーネントツリーの構造やデータ、クライアントコンポーネントへの参照などを解釈します。
  3. コンポーネントツリーの構築: 受信した情報をもとに、メモリ上でコンポーネントツリーを構築します。サーバーコンポーネントの部分はペイロードの構造から直接構築し、クライアントコンポーネントの部分は参照情報から対応するクライアントコンポーネントのコードをロードして配置します。
  4. レンダリング: 構築したコンポーネントツリーに基づいてDOMをレンダリングします。
  5. クライアントコンポーネントのハイドレーション: クライアントコンポーネントの部分に対してハイドレーションを実行し、イベントハンドラーをアタッチするなどしてインタラクティブにします。

この仕組みにより、クライアントはサーバーコンポーネントのコード自体を持つ必要がなく、レンダリング結果とクライアントコンポーネントのコードだけをダウンロードすれば良いことになります。

4.5. データ取得のライフサイクル

RSCにおけるデータ取得は、従来のクライアントサイドでのデータ取得とは大きく異なります。

  1. リクエスト: ユーザーがページにアクセスまたはクライアントサイドナビゲーションが発生。
  2. サーバーでのレンダリング: サーバーはリクエストに対応するルートのサーバーコンポーネントを特定し、レンダリングを開始。
  3. データ取得: サーバーコンポーネント内で async/await を用いて非同期処理(例:データベースクエリ、APIコール)を実行。Reactはデータが取得できるまでレンダリングを待機(またはサスペンドし、フォールバックを表示してからデータ取得完了後に再開)。
  4. レンダリング続行: データ取得が完了すると、サーバーは取得したデータを使ってコンポーネントをレンダリング。
  5. ペイロード送信: レンダリング結果をRSCペイロードとしてクライアントにストリーム送信。
  6. クライアントでの表示: クライアントは受信したペイロードを解析し、UIをレンダリング。データは既にペイロードに含まれているため、クライアントサイドでの追加のデータ取得は不要(ただし、クライアントコンポーネント内で動的にデータをフェッチすることは可能)。

この「レンダー中に非同期処理を待つ」というモデルは、ReactのSuspenseと深く連携しています。サーバーコンポーネント内で await する際、Reactはサスペンドし、親コンポーネントで定義されたSuspenseフォールバック(ローディング表示など)を表示できます。データ取得が完了すると、フォールバックが実際のコンテンツに置き換わります。この仕組みにより、データ取得とUIのレンダリングを緊密に統合し、よりスムーズなユーザー体験を提供できます。

5. RSCの導入方法(Next.js App Routerを例に)

RSCをプロダクションで使用する場合、現時点ではNext.jsのApp Routerが最も一般的で成熟したフレームワークです。ここでは、Next.js App Routerを使ったRSCの基本的な導入方法と開発パターンを解説します。

5.1. Next.js App Routerのセットアップ

まず、最新版のNext.jsを使用してプロジェクトを作成します。プロジェクト作成時にApp Routerを使用するかどうかを選択するプロンプトが表示されますので、Yesを選択します。

bash
npx create-next-app@latest my-rsc-app

プロジェクトが作成されたら、app ディレクトリが作成されていることを確認してください。この app ディレクトリがApp Routerのルートディレクトリとなり、この中に配置されるコンポーネントは、特別な指定がない限りデフォルトでサーバーコンポーネントとして扱われます。

5.2. デフォルトはサーバーコンポーネント

app ディレクトリ内に新しくファイルを作成し、コンポーネントを定義してみましょう。例えば、app/page.tsx(または.js)というファイルを作成します。これはデフォルトでそのルートのトップレベルのページコンポーネントになります。

“`jsx
// app/page.tsx
import Link from ‘next/link’;

export default function HomePage() {
// このコンポーネントはデフォルトでサーバーコンポーネントです
// useState, useEffect, ブラウザAPIなどは使えません

const currentTime = new Date().toLocaleTimeString();
console.log(HomePage rendered on server at ${currentTime}); // サーバーのログに出力される

return (

Welcome to My RSC App

This page is rendered on the server.

Current server time: {currentTime}

About Page

);
}
“`

このコンポーネントはサーバー上で実行され、生成されたHTML(厳密にはRSCペイロード)がクライアントに送られます。開発サーバーを起動し、ブラウザで確認してみてください。ソースコードを表示しても、このコンポーネントのJavaScriptコードは含まれていないことがわかります(Next.jsが必要とする最小限のクライアントランタイムはロードされます)。

5.3. use client ディレクティブ:クライアントコンポーネントの使用

ユーザーとのインタラクションや状態管理が必要な場合は、クライアントコンポーネントを使用します。ファイルの先頭に 'use client'; を追加するだけです。

例:カウンターコンポーネント

“`jsx
// components/Counter.tsx
‘use client’; // このディレクティブが必須

import { useState } from ‘react’;

export default function Counter() {
const [count, setCount] = useState(0);

// このコードはクライアントで実行されます
console.log(‘Counter component rendered on client’);

return (

Count: {count}

);
}
“`

この Counter コンポーネントはクライアントコンポーネントなので、useState やイベントハンドラー (onClick) を使用できます。

5.4. サーバーコンポーネントとクライアントコンポーネントの組み合わせ

実際のアプリケーションでは、これらを組み合わせて使用します。親コンポーネントがサーバーコンポーネントであることが一般的です。

“`jsx
// app/page.tsx (サーバーコンポーネント)
import Link from ‘next/link’;
import Counter from ‘../components/Counter’; // クライアントコンポーネントをインポート

export default function HomePage() {
const currentTime = new Date().toLocaleTimeString();
console.log(HomePage rendered on server at ${currentTime});

return (

Welcome to My RSC App

This page is rendered on the server.

Current server time: {currentTime}

  <h2>Interactive Counter (Client Component)</h2>
  <Counter /> {/* クライアントコンポーネントを子としてレンダリング */}

  <Link href="/about">About Page</Link>
</div>

);
}
“`

この例では、HomePage (サーバーコンポーネント) の中に Counter (クライアントコンポーネント) を配置しています。サーバーは HomePage をレンダリングする際に、Counter の部分でクライアントコンポーネントへの参照をペイロードに含めます。クライアントはペイロードを受信し、HomePage の構造を構築し、Counter の位置にクライアントコンポーネントのコードをロードして実行し、ハイドレーションを行います。

重要なのは、サーバーコンポーネントはクライアントコンポーネントを「ブラックボックス」として扱います。サーバーはクライアントコンポーネントの内部ロジックを知る必要はなく、単にその存在と渡されるプロパティ(シリアライズ可能なものに限る)だけをペイロードに含めるのです。

5.5. サーバーコンポーネントでのデータ取得

サーバーコンポーネントの強力な機能の一つは、サーバーサイドで直接データを取得できることです。async キーワードを使用できます。

Next.js App Routerでは、fetch APIが拡張されており、自動的なキャッシュと再検証(revalidation)の機能が組み込まれています。

“`jsx
// app/posts/page.tsx (サーバーコンポーネント)

interface Post {
id: number;
title: string;
body: string;
}

async function getPosts(): Promise {
// このfetchはサーバーサイドで実行される
const res = await fetch(‘https://jsonplaceholder.typicode.com/posts’, {
// Next.js fetch cache options:
// cache: ‘force-cache’ (default) – キャッシュを優先
// cache: ‘no-store’ – キャッシュしない (SSR的挙動)
// next: { revalidate: 60 } – 60秒ごとにキャッシュを再検証 (ISR的挙動)
});

if (!res.ok) {
// This will activate the closest error.tsx Error Boundary
throw new Error(‘Failed to fetch data’);
}

return res.json();
}

export default async function PostsPage() {
// ページコンポーネントはasyncにできる
const posts = await getPosts(); // サーバーサイドでデータ取得

console.log(PostsPage rendered on server with ${posts.length} posts);

return (

Posts

    {posts.map((post) => (

  • {post.title}

    {post.body}

  • ))}

);
}
“`

この例では、PostsPage コンポーネントが async 関数として定義されており、その中で await getPosts() を呼び出してデータを取得しています。このデータ取得はサーバーで行われるため、クライアントに送られるJavaScriptバンドルにはデータ取得のロジックが含まれません。取得したデータはサーバーコンポーネント内で直接使用され、レンダリング結果の一部としてクライアントに送信されます。

Suspenseと組み合わせることで、データ取得中のローディング状態を表現できます。

“`jsx
// app/posts/page.tsx
import { Suspense } from ‘react’;
import PostsList from ‘./PostsList’; // PostsListは上記のasyncコンポーネントを想定

export default function PostsPage() {
return (

Posts

Loading posts…\

}>
{/ Suspense境界内で非同期コンポーネントをレンダリング /}

);
}

// app/posts/PostsList.tsx (このファイルが上記のasyncコンポーネント本体)
interface Post { … }
async function getPosts(): Promise { … } // 同上

export default async function PostsList() {
const posts = await getPosts();
return (

    {posts.map((post) => (

  • {post.title}

    {post.body}

  • ))}

);
}
“`

5.6. サーバーアクション (use server)

フォーム送信など、クライアントからサーバーサイドの処理を呼び出したい場合があります。RSCでは、これを安全に行うための「サーバーアクション」という仕組みが提供されています。関数の先頭に 'use server'; ディレクティブを記述することで、その関数をクライアントサイドから呼び出し可能なサーバー関数として定義できます。

“`jsx
// app/components/AddPostForm.tsx
‘use client’; // これはクライアントコンポーネント

import { useState } from ‘react’;
import { addPost } from ‘../actions’; // サーバーアクションをインポート

export default function AddPostForm() {
const [title, setTitle] = useState(”);
const [body, setBody] = useState(”);
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setIsSubmitting(true);

try {
  // サーバーアクションを呼び出す
  await addPost(title, body);
  setTitle('');
  setBody('');
  // Optional: revalidate cache or redirect
} catch (error) {
  console.error("Failed to add post:", error);
  alert("Failed to add post. Please try again.");
} finally {
  setIsSubmitting(false);
}

};

return (

Add New Post


setTitle(e.target.value)}
disabled={isSubmitting}
/>

上部へスクロール