1. サーバーコンポーネントとクライアントコンポーネント
| 特長 | サーバーコンポーネント (デフォルト) | クライアントコンポーネント (「クライアントを使用する」) |
|---|---|---|
| レンダリング | サーバーのみ | サーバー(SSR) + クライアント(ハイドレーション) |
| JavaScript バンドル | 0 KB (JS は送信されません) | バンドルに含まれるもの |
| データの取得 | DB/APIへの直接アクセス | useEffect、TanStack クエリ |
| 状態/効果 | ❌ useState/useEffect なし | ✅ フルフックサポート |
| イベントハンドラー | ❌ onClick、onChange はありません | ✅ イベントのフルサポート |
| ブラウザAPI | ❌ 窓や書類がない | ✅ フルブラウザアクセス |
| シークレット/環境 | ✅ 安全に使用できます | ❌ クライアントに公開される |
2. いつ何を使用するのですか?
Server Component (default): Client Component ("use client"):
✅ Static content ✅ useState, useEffect
✅ Data fetching ✅ onClick, onChange
✅ Access database/API ✅ Browser APIs
✅ Sensitive logic ✅ Custom hooks with state
✅ Large dependencies ✅ Third-party libs (charts, editors)
3. 構成パターン
// ✅ Pattern 1: Server Component wraps Client Component
// app/dashboard/page.tsx (Server Component)
import { InteractiveChart } from './InteractiveChart';
export default async function Dashboard() {
const data = await fetchAnalytics(); // Server-side data fetching
return (
<div>
<h1>Dashboard</h1>
<InteractiveChart data={data} /> {/* Pass serializable data */}
</div>
);
}
// InteractiveChart.tsx
'use client';
export function InteractiveChart({ data }: { data: ChartData[] }) {
const [filter, setFilter] = useState('all');
// Interactive client-side logic
return <div>{/* chart */}</div>;
}
// ✅ Pattern 2: Children pattern (Server inside Client)
// ClientLayout.tsx
'use client';
export function ClientLayout({ children }: { children: React.ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(true);
return (
<div className="flex">
<Sidebar open={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
<main>{children}</main> {/* children can be Server Components! */}
</div>
);
}
// page.tsx (Server Component)
import { ClientLayout } from './ClientLayout';
export default async function Page() {
const data = await fetchData(); // Server-side
return (
<ClientLayout>
<ServerContent data={data} /> {/* Server component as children */}
</ClientLayout>
);
}
// ❌ Anti-pattern: Importing Server Component INTO Client Component
'use client';
import ServerComponent from './ServerComponent'; // ⚠️ Becomes client component!
// ✅ Fix: Pass as children or props
4. ストリーミングとサスペンス
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* Mỗi Suspense boundary stream independently */}
<Suspense fallback={<CardSkeleton />}>
<RevenueChart /> {/* Async server component */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders /> {/* Async server component */}
</Suspense>
<Suspense fallback={<ListSkeleton />}>
<TopProducts /> {/* Async server component */}
</Suspense>
</div>
);
}
// Async Server Component
async function RevenueChart() {
const data = await fetchRevenue(); // Can take 2-3 seconds
return <Chart data={data} />;
}
async function RecentOrders() {
const orders = await fetchRecentOrders(); // Independent fetch
return <OrderTable orders={orders} />;
}
5. シリアル化ルール
// Server → Client props must be serializable:
// ✅ string, number, boolean, null, undefined
// ✅ Array, plain Object
// ✅ Date (serialized as string)
// ❌ Functions (cannot pass onClick from server)
// ❌ Class instances
// ❌ Symbols
// ❌ DOM nodes
// ✅ OK
<ClientComponent data={jsonData} count={42} />
// ❌ Error
<ClientComponent onClick={() => {}} ref={myRef} />
次の記事: データのフェッチとキャッシュ — サーバー側のフェッチ、ISR、再検証、およびキャッシュ戦略。