以下範例將說明如何使用 renderToPipeableStream
在 Node.js 環境中使用 Streaming SSR:
import { renderToPipeableStream } from "react-dom/server";
app.use("/", (request, response) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/main.js"],
onShellReady() {
response.setHeader("content-type", "text/html");
pipe(response);
},
});
});
bootstrapScripts
: 要在 Client 端載入的 JavaScript 檔案。onShellReady
:在初始架構 (shell) 渲染後立即觸發。pipe
: 將生成的 HTML 傳到 Client 端。onShellReady
和 onAllReady
除了 onShellReady
,還有 onAllReady
的選項,以下是兩者的差別:
onShellReady
:在初始 shell (頁面架構) 渲染後立即觸發。onAllReady
:在所有都渲染完成時觸發。範例說明:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}
透過 Suspense
,React 可以在資料尚未完全載入前,先傳輸頁面的主要架構(shell)。之後再逐步載入資料並替換 fallback UI。
除了 renderToPipeableStream
之外,還有其他方法回傳 HTML:
renderToStaticNodeStream
: 適用於 Node.js 環境,生成的是非互動的靜態頁面。renderToReadableStream
: 適用於 Web Streams 環境(例如 Deno 或 edge runtimes),使用 Web Streams API 傳輸 HTML。renderToString
: 直接將 React 組件渲染為靜態 HTML 字串,但不支援 Streaming。renderToStaticMarkup
: 與 renderToString
類似,但生成的是非互動的靜態頁面。在 Server 端完成 SSR,將靜態 HTML 傳送至客戶端後,React 會透過 hydrateRoot
將這些靜態的 HTML 轉換為可互動的 React 元件。
基本架構:
const root = hydrateRoot(domNode, reactNode, options?)
domNode
: 在 html 中對應的根元素的 DOM 節點reactNode
: 用來生成對應 HTML 的 React 元件,像是 <App />
。使用範例:
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
hydrateRoot(document.getElementById("root"), <App />);
需要注意的是,hydrateRoot
的預期內容必須與 Server 端渲染的 HTML 完全一致,否則可能會導致 Hydration 錯誤。
在 React Fiber 中有一個屬性 stateNode
,用來指向 Fiber 所對應的真實 DOM 節點。hydrateRoot
的實際流程會是在 render 階段創建 DOM 節點,並在 commit 階段更新 DOM 節點。
由於 Server 端生成的 HTML 和客戶端渲染的 React 樹必須完全一致,有些微的不同都有可能導致 Hydration 錯誤。這方面對使用者體驗也很重要,透過先將 HTML 展示給使用者會營造出應用快速載入的效果。如果內容不一致就會讓使用者覺得很奇怪。
以下是常見的 Hydration 錯誤:
typeof window !== 'undefined'
React 可以修復部分 Hydration 問題,但修復的過程可能會導致性能降低,甚至有可能將事件處理函數會綁定到錯誤的元素。
參考資料:
https://react.dev/reference/react-dom/server
https://react.dev/reference/react-dom/server/renderToPipeableStream
https://react.dev/reference/react-dom/client/hydrateRoot
https://github.com/Reactwg/React-18/discussions/37
https://juejin.cn/post/7165699863416406029
https://blog.logrocket.com/react-hydration-pre-rendered-html/