1. 伺服器元件與客戶端元件
| 特點 | 伺服器組件(預設) | 客戶端元件(“使用客戶端”) |
|---|---|---|
| 渲染 | 僅伺服器 | 伺服器(SSR)+客戶端(水合) |
| JavaScript 套件 | 0 KB(未發送 JS) | 包含在捆綁包中 |
| 數據獲取 | 直接資料庫/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、重新驗證和快取策略。