前兩天介紹了 Rendering Infrastructure 優化的其中兩個項目 - 新的路由架構 App Router 與使用 layout.tsx 更簡單地實現 persistant layout,假如還沒看的朋友可參考 Day 08 和 Day 09 的文章!
今天來看最後一個項目 - Server Components。
Server Components 顧名思義,即是在 server-side 渲染的 components ( 這邊的 server 其實用 “React Server” 會更精準,後面會談到 )。事實上,Server Components 並不是由 Vercel 推出的新概念,而是 React 早在 2020 即推出的功能 ( 可參考 React Blog 公告 )。只是如同 Day 02 提到, 假如單純用 React 專案實作 React Server Components ( RSC ),你需要起 server、做一些 bundler 的設定等等。而 App Router 則是預設 components 是 Server Components,假如要轉為 Client Components 只需要在檔案最上方標示 ‘use client’ 即可,大幅降低 RSC 的使用門檻。
舉例來說,假如我們有個靜態頁面,要渲染一支 API 的資料,傳統的做法可能是將渲染內容存在一個 state 中,用 useEffect 打 API,拿到 API response 後更新 state 觸發 re-render 更新頁面內容:
import { useEffect, useState } from 'react';
export default function Home() {
const [apiData, setApiData] = useState<string | null>(null);
useEffect(() => {
const fetchData = async (url: string) => {
try {
const response = await fetch(url);
const jsonData = await response.json();
setApiData(jsonData);
} catch (err) {
window.alert('發生錯誤,請稍後再試');
}
};
fetchData('http://localhost:3000/api/hello-world');
}, []);
return <div>{apiData}</div>;
}
假如使用 Server Components 來產生一樣的靜態網頁,我們可以在 server-side fetch 完資料後,直接以 response 內容渲染 components:
const fetchData = async (url: string) => {
try {
const response = await fetch(url);
return await response.json();
} catch (err) {
return null;
}
};
export default async function Home() {
const apiData = await fetchData('http://localhost:3000/api/hello-world');
return <div>{apiData}</div>;
}
那使用 Server Components 有什麼好處與限制呢?我們留到明天再和大家分享!再進入實作講解前,想花點時間回答兩個常見的問題:
答案是錯!Client Components 也可以在 server-side 渲染。事實上,Client Components 和 Server Components 的 Client 與 Server,更精準來說,指的是 "React Client” 與 "React Server”,跟我們在講 Client-Side Rendering 與 Server-Side Rendering 中的 Client 和 Server 不太一樣。
等等,那什麼是 React Server 和 React Client?
React Server 和 React Client 並不是一個具有「實體」的服務或設備,像是實體 server 或瀏覽器,而是 React 渲染過程中兩個角色。
簡單來說,React Server 指的是渲染 RSC 的環境,預設會在 build time 運行,但也可以跑在實體 server上,主要任務是與後端服務互動,並渲染 RSC;而 React Client 指的是接受與使用 React Server output 的環境,server 和瀏覽器上都可以運行。假如跑在瀏覽器上,主要任務會是操控 DOM;跑在 server 上則是產生初始的 HTML string ( SSR ),而在 React Client 被渲染的 components 就稱為 Client Components。
除此之外,RSC 的概念並不是產生一個全新的 components 運作模式,而是在既有的架構中加入一層 RSC Server layer,來與後端互動,生成完 Server Components 後再將 components 以 props 傳給 React Client,進而接著產生 HTML。
( 圖片來源:https://github.com/reactwg/server-components/discussions/4 )
所以 Client Components 可以在 server-side 渲染也可以在 client-side 渲染。App Router 中,假如用戶第一次拜訪網頁,Client Components 會在 server-side render 一個靜態 HTML,再到 client-side 執行 hydration,加快網頁初始載入的速度;假如是非第一次拜訪的 navigation,代表 JavaScript bundle 已經下載並 parse 完了,Client Components 就會完全在 client-side 渲染。
兩者是不同的!兩者執行的階段和最終的 output 不同。
Server-Side Rendering ( SSR ) 是在 React Client 中,根據你的程式碼產生一份 HTML string。套用 Dan Abramov 在 React Server Components deep dive 中一句定義 SSR 的話:
Turning JSX into an HTML string is usually known as "Server-Side Rendering" (SSR).
而 Sever Components 則是在 React Server 中生成出 React components ( 一個 object )。這些 components 生成後,會被傳到 React Client 產生 HTML,所以 Server Components 是在 SSR 之前即生成。這也意味著走 SSR 不一定要用 Server Components,用 Server Components 也不一定要走 SSR!
假如想更深入了解 Server Components 是如何生成的,可以參考 Dan 寫的 deep dive。
( 圖片來源:https://github.com/reactwg/server-components/discussions/5 )
總結來說,小弟個人覺得 Client Components 這個命名蠻容易誤導大家,以為 Client Components 就是在 client-side 渲染的 components。但如同上文提到,Client Components 也可以在 server-side 渲染。
那該怎麼更簡單地區分 Client Components 與 Server Components 呢?我認為可以說 Client Components 是可以使用 client-only 功能 (ex: event listener、react hooks、local storage...) 的 components;Server Components 則是沒有要使用這些功能,而且可以在頁面渲染前,在 server 提前做一些事 ( ex: 到 DB 撈資料 ) 的 components。這部分不用著急,等看完後面幾天的文章後,可能會更好理解!
小補充,也因為 client-only 的功能通常具備互動性,我也有看到國外網友建議 Client Components 改名叫 Interact Components 之類的,我們就靜觀 Vercel 之後會不會調整名稱。
今天介紹了 Server Components 是什麼,以及釐清 SSR 與 Server Components 的關係與差異,明天會接著和大家分享 Server Components 的優點、使用時機和限制。
謝謝大家耐心的閱讀,我們明天見!
Hi S.C 您好,
閱讀完您的文章後, 對於在server side 渲染client components有些疑問
以App Router 的情況來說, 只有加上"use client" 的元件, 才會被視為 client component
以文件上來說
https://nextjs.org/docs/app/building-your-application/rendering/client-components#full-page-load
在server side渲染client components的狀況
是否指的就是產生HTML 文件嗎? 或是會將Client component內, 不需要與user互動的部分也會先提前渲染?
哈囉~感謝你的留言!
你的理解沒有錯喔,這邊的「渲染」簡單來說就是產生初始的 HTML 檔案沒錯。假如專案是 server-side rendering,在渲染 client components 時,會先在初始 HTML 給予一個預設值 ( ex: React state 的初始值 ),動態事件再由瀏覽器執行 javascript 處理 ( hydration )
感謝您的回覆.
所以以我的理解, 整個流程跟角色會像是
不知道這樣的理解是否跟您的文章所提到的名詞跟流程是否一樣