如何使用伺服器與客戶端元件
預設情況下,版面配置 (layouts) 和頁面 (pages) 都是伺服器元件 (Server Components),這讓您可以在伺服器端獲取資料並渲染部分 UI,選擇性地快取結果並串流至客戶端。當需要互動性或使用瀏覽器 API 時,您可以使用客戶端元件 (Client Components) 來添加功能層。
本頁將說明伺服器元件與客戶端元件在 Next.js 中的運作原理、使用時機,並提供如何在應用程式中組合它們的範例。
何時使用伺服器與客戶端元件?
客戶端與伺服器環境具有不同的能力。伺服器元件與客戶端元件讓您可以根據使用情境,在各自環境中執行邏輯。
當您需要以下功能時,請使用客戶端元件:
- 狀態 (State) 與事件處理器 (event handlers)。例如
onClick
、onChange
。 - 生命週期邏輯 (Lifecycle logic)。例如
useEffect
。 - 僅限瀏覽器的 API。例如
localStorage
、window
、Navigator.geolocation
等。 - 自訂鉤子 (Custom hooks)。
當您需要以下功能時,請使用伺服器元件:
- 從資料庫或靠近來源的 API 獲取資料。
- 使用 API 金鑰、令牌等機密資訊而不暴露給客戶端。
- 減少傳送至瀏覽器的 JavaScript 數量。
- 改善首次內容繪製 (First Contentful Paint, FCP),並逐步串流內容至客戶端。
例如,<Page>
元件是一個伺服器元件,它獲取文章資料並將其作為 props 傳遞給處理客戶端互動的 <LikeButton>
。
伺服器與客戶端元件在 Next.js 中如何運作?
在伺服器端
在伺服器端,Next.js 使用 React 的 API 來協調渲染工作。渲染工作會按個別路由區段 (版面配置與頁面) 拆分成多個部分:
- 伺服器元件會被渲染成一種稱為 React 伺服器元件負載 (RSC Payload) 的特殊資料格式。
- 客戶端元件與 RSC 負載會用於預渲染 (prerender) HTML。
什麼是 React 伺服器元件負載 (RSC Payload)?
RSC 負載是已渲染 React 伺服器元件樹的精簡二進位表示法。React 會在客戶端使用它來更新瀏覽器的 DOM。RSC 負載包含:
- 伺服器元件的渲染結果
- 客戶端元件應渲染位置的佔位符及其 JavaScript 檔案的參考
- 從伺服器元件傳遞給客戶端元件的任何 props
在客戶端 (首次載入)
接著,在客戶端:
- HTML 會立即顯示路由的非互動式預覽給使用者。
- RSC 負載用於協調客戶端與伺服器元件樹。
- JavaScript 用於水合 (hydrate) 客戶端元件並使應用程式具有互動性。
什麼是水合 (hydration)?
水合是 React 將事件處理器 (event handlers) 附加至 DOM 的過程,使靜態 HTML 具有互動性。
後續導航
在後續導航時:
- RSC 負載會被預先獲取並快取,實現瞬間導航。
- 客戶端元件完全在客戶端渲染,無需伺服器渲染的 HTML。
範例
使用客戶端元件
您可以在檔案頂部、import 語句之前添加 "use client"
指令來建立客戶端元件。
"use client"
用於宣告伺服器與客戶端模組圖 (樹) 之間的邊界。
一旦檔案標記了 "use client"
,其所有導入的子元件都會被視為客戶端套件的一部分。這意味著您不需要為每個客戶端元件都添加此指令。
減少 JS 套件大小
為了減少客戶端 JavaScript 套件的大小,請將 'use client'
添加到特定的互動式元件,而不是將大部分 UI 標記為客戶端元件。
例如,<Layout>
元件包含靜態元素如標誌和導航連結,但也包含一個互動式搜尋欄。<Search />
是互動式的且需要是客戶端元件,但版面配置的其餘部分可以保持為伺服器元件。
從伺服器元件傳遞資料至客戶端元件
您可以使用 props 將資料從伺服器元件傳遞至客戶端元件。
或者,您可以使用 use
Hook 從伺服器元件串流資料至客戶端元件。請參閱範例。
須知:傳遞給客戶端元件的 props 需要能被 React 序列化 (serializable)。
交錯使用伺服器與客戶端元件
您可以將伺服器元件作為 prop 傳遞給客戶端元件。這讓您可以在客戶端元件中嵌套伺服器渲染的 UI。
常見的模式是使用 children
在 <ClientComponent>
中建立一個_插槽 (slot)_。例如,一個在伺服器端獲取資料的 <Cart>
元件,嵌套在使用客戶端狀態控制顯示的 <Modal>
元件中。
然後,在父級伺服器元件 (例如 <Page>
) 中,您可以將 <Cart>
作為 <Modal>
的子元件傳遞:
在此模式中,所有伺服器元件 (包括作為 props 傳遞的元件) 都會在伺服器端預先渲染。產生的 RSC 負載將包含客戶端元件在元件樹中的渲染位置參考。
上下文提供者 (Context providers)
React 上下文 (context) 常用於共享全域狀態,例如當前主題。然而,React 上下文不支援在伺服器元件中使用。
要使用上下文,請建立一個接受 children
的客戶端元件:
然後,將其導入伺服器元件 (例如 layout
):
現在,您的伺服器元件將能直接渲染提供者,而應用程式中的所有其他客戶端元件都能使用此上下文。
須知:您應盡可能在樹的深層渲染提供者 — 注意
ThemeProvider
僅包裹{children}
而非整個<html>
文件。這讓 Next.js 更容易優化伺服器元件的靜態部分。
第三方元件
當使用依賴客戶端功能的第三方元件時,您可以將其包裹在客戶端元件中以確保正常運作。
例如,<Carousel />
可從 acme-carousel
套件導入。此元件使用 useState
,但尚未添加 "use client"
指令。
如果您在客戶端元件中使用 <Carousel />
,它將如預期運作:
然而,如果您嘗試直接在伺服器元件中使用它,將會出現錯誤。這是因為 Next.js 不知道 <Carousel />
使用了僅限客戶端的功能。
要解決此問題,您可以將依賴客戶端功能的第三方元件包裹在自訂的客戶端元件中:
現在,您可以直接在伺服器元件中使用 <Carousel />
:
給函式庫作者的建議
如果您正在建立元件函式庫,請為依賴客戶端功能的入口點添加
"use client"
指令。這讓使用者能直接將元件導入伺服器元件,而無需建立包裹元件。值得注意的是,某些打包工具可能會移除
"use client"
指令。您可以在 React Wrap Balancer 和 Vercel Analytics 儲存庫中找到如何配置 esbuild 以包含"use client"
指令的範例。
防止環境污染
JavaScript 模組可以在伺服器元件和客戶端元件之間共享,這意味著有可能意外將僅限伺服器的程式碼導入客戶端。例如,考慮以下函式:
此函式包含一個 API_KEY
,絕不應該暴露給客戶端。
在 Next.js 中,只有以 NEXT_PUBLIC_
為前綴的環境變數會被包含在客戶端套件中。如果變數沒有前綴,Next.js 會將其替換為空字串。
因此,即使 getData()
可以被導入並在客戶端執行,它也不會如預期般運作。
為了防止在客戶端元件中意外使用,你可以使用 server-only
套件。
然後,將套件導入包含僅限伺服器程式碼的檔案中:
現在,如果你嘗試將此模組導入客戶端元件,將會在構建時出現錯誤。
小知識:對應的
client-only
套件 可用來標記包含僅限客戶端邏輯的模組,例如存取window
物件的程式碼。