跳至主要内容

React Server Components


React 改變了我們建立 UI 的方式。使用 React Server Components 的新模型更加結構化、方便、可維護,讓 Components 能在 server 或 client 得到最好的發揮,並提供更好的用戶體驗。

image alt

如上圖,會發現並不是所有的 component 都需要和 user 互動,故代表可以將部分靜態頁面移動至 server component 呈現。至於其他互動式 UI,可以將它變成 client component。

React Server Component 允許開發者有選擇性地在 server 端和 client 端渲染不同部分的元件


What is server-side render?

原理: SSR 專注於初始頁面加載,將預渲染的 HTML 發送到 client 端,然後使用下載的 JavaScript 進行 hydrated,下載完成後,此時畫面才具有互動性。

流程:

圖片參考: https://www.mux.com/blog/what-are-react-server-components#how-did-we-get-here

可以思考:

在 client 端下載 JavaScript 之前,用戶仍然無法與 UI 互動。
大多數 JavaScript 計算量仍然在 client 端上,client 端可以在任何類型的設備上運行。
為什麼不將其轉移到更強大、可預測的 server 上呢?
我們真的需要在 client 端 運行所有 JavaScript 嗎?我們真的需要重複所有渲染工作只是為了 hydrated 嗎?


What did React Server Components do?

為了解決上述問題,React 創建了 React Server Components(RSC)。

RSC 是一種新型態的 component

  1. RSC 能夠讓 React 的元件部分在 server 端被 rendered、部分在 client 端被 rendered。
  2. 能單獨獲取數據並完全在 server 端渲染,生成的 HTML 會 streamed 至 client 端的 React component tree,並根據需求與其他 Server or Client Components 交錯互動。

一般來說 client 端在接收到 JS bundle 後會進行 hydration,不過 RSC 中的 Server Components 只會在 Server Side 做渲染,不會在 client 端進行 hydration。

交互式元件(綠色)被發送到 client 端,而靜態元件(藍色)則留在服務器上。
圖片參考: https://www.mux.com/blog/what-are-react-server-components#what-are-react-server-components-what-are-they-good-for

所以可以向 client 端傳送更少的 JavaScript,從而減少 bundle 的大小也減少 hydration 過程中的工作量。


RSCs: Interleaving and Suspense integration

RSC 與 client 端完全交錯,這意味著 client 端 component 和 server 端 component 可以在同一個 React 樹中呈現。

在傳統的 CSR 中,component 使用 React Suspense暫停其渲染過程(並顯示 fallback 狀態),同時等待異步工作完成。使用 RSC,數據獲取和渲染都發生在 server 上,因此 Suspense 也在 server 端管理等待時間,縮短了總往返時間,以加快渲染回退和完成頁面的速度。

RSC 中 Server Side 在渲染過程如果遇到 Client Component,它並不會去實際執行或渲染它,而是會把一些資訊註記起來傳給 client side。

值得注意的是,client component 在初始加載時仍然是 SSR 的。RSC 模型不會取代 SSR 或 Suspense,而是與它們一起工作,根據用戶的需要向他們提供應用程序的所有部分。


RSC 優勢

  1. 減少 bundle size: RSC 中的 Server Components 因為只在 Server 上做渲染,所以元件的程式碼 bundle 不用被下載到 Client Side, 如果套件只被 Server Components 使用(React 官方的範例是一個處理 markdown 語法的套件),就不用擔心它會增加應用整體的 bundle size
  2. 運用 Server Side 的能力: 可以直接在 server 做 data fetching,可以解決 Client-Server 往返過多,甚至造成 waterfall 請求的狀況,典型的情境就是透過 nested 的 useEffect 來 call API 獲取資料,我們需要等在上層的 component 抓取資料並 render 出下層 component 後,才知道下層的元件需要什麼樣的資料,這種模式對效能來說可能會產生很大的影響。

RSC 局限性

  1. CSS-in-JS不適用於 Server Components,需要改成用 styled-component or Tailwind
  2. Context 在 Server component 不起作用

React Server Components v.s. Server Side Rendering

  • SSR:

    • 在 server 中 render 一個 HTML 傳送到 client side,並在 client side 透過 react runtime 做 hydration,完成之後,頁面才是一個可以互動的完整應用。
    • 除了 initial page load 以外,後續頁面的跳轉,都是走 client side 的 navigation,Next 會 call 一個 API endpoint 去執行 getServerSideProps function 去抓取需要的資料,但並不會重新產生一個 HTML。
      • => 即便是 SSR 的應用,在 navigation 時,行為就跟 SPA 頁面行為一致了。
    • SSR 的重點在於「頁面的初始渲染」。
  • RSC:

    • 永遠都是在 server 上渲染的
    • 當這些元件需要 re-render 時,它們會在 server side 重新做 data-fetching,然後重新 merge 回 client side 中現有的 React Component Tree。
    • 當頁面中部分 Server Components 重新再跟伺服器要資料,與此同時 client side 的 state 是可以被保留的。

兩者是完全不同的概念,也不互相衝突,使用 Server Components 不一定要走 SSR,使用 SSR 也不一定要用 Server Components。


Next.js 13 Server & Client Components

預設情況下走 Server Component,若有使用者互動的需求則走 Client Component


Server component

server 端渲染 component,擁有訪問數據庫、訪問本地文件等能力。無法綁定事件對象,即不擁有交互性。

使用情境:

  • 獲取數據
  • 直接訪問數據庫
  • 直接訪問敏感 API

特色

  • Sever Component 只會在 server 被執行,所有的程式碼不會傳到 client,因此可以減少 client-side JavaScript 的 bundle sizes (Zero effect on bundle size)
  • 開發者可以決定元件的優先級,選擇哪些元件在 render 時要優先被載入

Client component

Client 端渲染 component 後,畫面上會擁有交互性。(在 Next.js 中,它們在 server 上預渲染並在 client 端上進行 hydrated。)

使用情境:

  • 添加交互性和 event listeners,例如 onClick 和 onChange
  • 使用 useState、useEffect、useContext
  • 訪問 browser-only APIs
  • 使用 React Class Components

When to use Server and Client Components?

What do you need to do?Server ComponentClient Component
Fetch data.✔️
Access backend resources (directly)✔️
Keep sensitive information on the server✔️
Keep large dependencies on the server✔️
Add interactivity and event listeners (onClick(), onChange(), etc)✔️
Use State and Lifecycle Effects (useState(), useReducer(), useEffect(), etc)✔️
Use browser-only APIs✔️
Use custom hooks that depend on state, effects or browser-only APIs✔️
Use React Class components✔️
參考: https://nextjs.org/docs/getting-started/react-essentials#when-to-use-server-and-client-components

RSC 限制

  • Importing Server Components into Client Components
app/example-client-component.tsx
"use client";

import ServerComponent from "./ServerComponent";

export default function ClientComponent() {
return (
<div>
<h1>Server Component Below</h1>
<ServerComponent />
</div>
);
}

Server component 必須以 children props 的方式傳入,因為 Server Side 在渲染過程如果遇到 Client Component,它並不會去實際執行或渲染它,而是會把一些資訊註記起來傳給 client side。

建議寫法: Passing Server Components to Client Components as Props

app/example-client-component.tsx
"use client";

// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from "./example-server-component";

export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode,
}) {
const [count, setCount] = useState(0);

return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExampleServerComponent />
</>
);
}

這種寫法 React 才知道它必須先在 Server Side 渲染這個 Server Component 後,才能回傳資料給 client, 透過這種方式才能在 Server Side 完整解析樹狀結構。


總結

  • Balancing Server and Client Components

RSC 並不是要取代 Client Component ,可以利用 RSC 進行動態 fetch 資料,並利用 Client Components 來實現豐富的交互性。選擇何時使用 Server / Client Components 是個挑戰。


參考資料

  1. https://nextjs.org/docs/getting-started/react-essentials
  2. https://www.mux.com/blog/what-are-react-server-components
  3. https://vercel.com/blog/understanding-react-server-components
  4. Dan Abramov 的 RSC 實作細節 : https://github.com/reactwg/server-components/discussions/5?fbclid=IwAR1Y1NUjrF4POWooiaTTGCERCt9KLzcqr5EuXrtrPZJnwDXpGzn2A3IWb2M
  5. https://dev.to/zenstack/fun-with-nextjs-13-server-components-o37