React Server Components完全ガイド - Next.js App Routerで実践
React Server Componentsの概念から実装まで、Next.jsのApp Routerを使った実践的な解説。Client ComponentsとServer Componentsの使い分けやパフォーマンス最適化のテクニックを詳しく解説します。
昨日リリースされたNext.js 15のApp Router v2。破壊的変更もあるけど、パフォーマンスが劇的に向上。実プロジェクトで試した結果をレポート。
昨日 Next.js 15 がリリースされました!App Router v2 の改善がすごいという噂を聞いて、既存プロジェクトで即座にアップグレードしてみました。
結論から言うと、移行は大変だけど価値はあります。
中規模の EC サイトのリニューアル案件で:
「パフォーマンス改善も兼ねて RSC 導入しよう」という軽いノリで始めたのが間違いでした。
最初、どのコンポーネントに "use client"
を付けるべきか分からず、エラーが出るたびに追加していたら、ほぼ全部 Client Component になってしまいました。
Error: Event handlers cannot be passed to Client Component props.
<button onClick={function} children=...>
^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
このエラーを見るたびに "use client"
を追加…の繰り返し。
Server Component と Client Component でデータの取得方法が違うことに混乱しました。
// これはServer Componentなのに...
export default function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
// Server ComponentでuseEffectは使えない!
fetch('/api/products').then(...)
}, []);
}
// Server Componentとして正しく実装
export default async function ProductList() {
// 直接データベースから取得
const products = await db.product.findMany({
take: 20
});
return <ProductGrid products={products} />;
}
// これはServer Componentなのに...
export default function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
// Server ComponentでuseEffectは使えない!
fetch('/api/products').then(...)
}, []);
}
// Server Componentとして正しく実装
export default async function ProductList() {
// 直接データベースから取得
const products = await db.product.findMany({
take: 20
});
return <ProductGrid products={products} />;
}
開発環境では動くのに、本番ビルドするとエラーが…
Error: Hydration failed because the initial UI does not match what was rendered on the server.
原因は、Server Component で Date
オブジェクトを使っていたこと:
// NG: タイムゾーンの違いでエラー
<p>更新日: {new Date(product.updatedAt).toLocaleString()}</p>
// OK: フォーマットしてから渡す
<p>更新日: {format(product.updatedAt, 'yyyy/MM/dd HH:mm')}</p>
1 週間格闘した後、根本的にアプローチを変えました。
「すべてを Server Component にして、必要な部分だけ Client Component にする」 のではなく、 「ページの構造を考えて、適切に分離する」
// app/layout.tsx (Server Component)
export default function RootLayout({ children }) {
// ナビゲーションのデータをサーバーで取得
const categories = await getCategories();
return (
<html>
<body>
<Header categories={categories} />
{children}
<Footer />
</body>
</html>
);
}
// components/Header.tsx (一部Client Component)
export default function Header({ categories }) {
return (
<header>
<Logo />
<Navigation categories={categories} />
<SearchBox /> {/* これだけClient Component */}
</header>
);
}
商品一覧ページの実装例:
// app/products/page.tsx (Server Component)
export default async function ProductsPage({ searchParams }) {
const products = await getProducts(searchParams);
return (
<div>
<ProductFilters /> {/* Client Component */}
<ProductList products={products} /> {/* Server Component */}
</div>
);
}
// components/ProductList.tsx (Server Component)
export default function ProductList({ products }) {
return (
<div className="grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// components/ProductCard.tsx (一部Client Component)
export default function ProductCard({ product }) {
return (
<article>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.price}円</p>
<AddToCartButton productId={product.id} /> {/* Client Component */}
</article>
);
}
パフォーマンスが劇的に改善したのは、適切なキャッシュ設定でした:
// 商品データは1時間キャッシュ
export async function getProducts() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1時間
});
return products.json();
}
// カテゴリーは24時間キャッシュ
export async function getCategories() {
const categories = await fetch('https://api.example.com/categories', {
next: { revalidate: 86400 } // 24時間
});
return categories.json();
}
Before (Pages Router):
After (App Router + RSC):
特に初期表示が速くなったのは体感できるレベルでした。
最初から完璧を目指さない
エラーメッセージを読む
デバッグツールを活用
パフォーマンス測定は必須
React Server Components は確かに学習曲線が急ですが、正しく使えば大きなメリットがあります。
ただし、既存プロジェクトの移行は慎重に。新規プロジェクトから始めることをおすすめします。
あと、公式ドキュメントは必読です。私は最初読まずに始めて後悔しました…
Next.jsのApp Router Playgroundで、実際の動作を確認しながら学ぶのがおすすめです。