こんにちは、かつコーチです。
前回は loading.tsx・error.tsx・not-found.tsx を使って、状態ごとのUIを整える方法を解説しました。
ここまでで一通りルーティング周りは押さえられましたが、実はNext.jsのApp Routerには、これまでのReactの常識を変える大きな仕組みがあります。
それが今回解説するServer Componentsです。
Server Componentsとは?
サーバー側だけで動くコンポーネント
Server Components(サーバーコンポーネント)とは、ブラウザには送られず、サーバー側だけでレンダリングが完結するコンポーネントのことです。
これまでReactで作るコンポーネントは、基本的にすべてブラウザ側のJavaScriptとして動くClient Componentsでした。
App Routerでは、app ディレクトリ内のコンポーネントは何も書かなければデフォルトでServer Componentになります。
なぜそれが必要なのか
従来のReactアプリでは、データを取得する際に、まずJavaScriptをブラウザに送り、ブラウザ側で useEffect などを使ってAPIを叩く、という流れが一般的でした。
この方式は、JavaScriptのダウンロード・実行を待ってからデータ取得が始まるため、表示までに時間がかかりがちです。
Server Componentsを使うと、サーバー側でデータ取得とHTML生成まで済ませてからブラウザに送れるため、表示までの時間短縮とブラウザに送るJavaScriptの削減という2つのメリットが得られます。
基本の書き方
手順1:何もしなければServer Component
// app/products/page.tsx
async function getProducts() {
const res = await fetch("https://api.example.com/products");
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<ul>
{products.map((product: { id: number; name: string }) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
page.tsx に "use client" を書いていないため、これはServer Componentです。
コンポーネントの中で直接 await fetch() を書けるのが最大の特徴で、useEffect や useState を使ったデータ取得は不要になります。
手順2:Client Componentとの違いを比較する
| 項目 | Server Components | Client Components |
|---|---|---|
| 実行場所 | サーバーのみ | サーバー(初回)+ブラウザ |
useState・useEffect | 使えない | 使える |
onClick などのイベント | 使えない | 使える |
| データベース・APIキーへの直接アクセス | 可能 | 不可(セキュリティ上NG) |
| ブラウザに送られるJS量 | 増えない | コンポーネント分だけ増える |
つまずきやすい設定・注意点
Server Componentの中では、useState や onClick などブラウザに依存する機能は一切使えません。
インタラクティブな部分が必要な場合は、次回解説する "use client" を使ってClient Componentとして切り出す必要があります。
よくあるつまずきポイント・エラー対処
一次情報:Server Componentにボタンをつけて怒られた話
私が最初にServer Componentsを触ったとき、Reactの感覚のまま page.tsx に直接 onClick を書いてしまいました。
❌ Before:Server Componentにイベントハンドラを書く
// app/products/page.tsx("use client"なし)
export default async function ProductsPage() {
const products = await getProducts();
return (
<ul>
{products.map((product: { id: number; name: string }) => (
<li key={product.id} onClick={() => alert(product.name)}>
{product.name}
</li>
))}
</ul>
);
}
このコードを実行すると、ビルド時に「Event handlers cannot be passed to Client Component props」というエラーが出て止まりました。
最初はエラーメッセージの意味が分からず戸惑いましたが、「onClick はブラウザで動く必要があるのに、このコンポーネントはサーバーでしか動かない」というのが原因でした。
✅ After:クリック部分だけClient Componentに切り出す
// app/products/ProductItem.tsx
"use client";
export function ProductItem({ name }: { name: string }) {
return <li onClick={() => alert(name)}>{name}</li>;
}
// app/products/page.tsx(Server Componentのまま)
import { ProductItem } from "./ProductItem";
export default async function ProductsPage() {
const products = await getProducts();
return (
<ul>
{products.map((product: { id: number; name: string }) => (
<ProductItem key={product.id} name={product.name} />
))}
</ul>
);
}
データ取得部分は page.tsx にServer Componentとして残し、クリックが必要な部分だけを ProductItem.tsx としてClient Componentに切り出すことで解決しました。
「インタラクティブな部分だけを小さく切り出す」という発想が、Server Components時代のReact設計で重要なポイントです。
まとめ
この記事のポイント
- App Routerでは、
appディレクトリ内のコンポーネントはデフォルトでServer Component - Server Componentはサーバー側だけで動き、コンポーネント内で直接
await fetch()できる useStateやonClickなどブラウザに依存する機能はServer Componentでは使えない- インタラクティブな部分は、小さいコンポーネントに切り出してClient Componentにする
次に読むべき記事
Server ComponentとClient Componentの違いが分かったところで、次は実際の判断基準を深掘りします。
次回は、"use client" はいつ・どこに書くべきか、使い分けの判断基準を解説します。
→ 次の記事:’use client’はいつ・どこに書く?使い分けの判断基準