prerenderToNodeStream

prerender 使用 Node.js 串流 將 React 樹狀結構渲染成靜態 HTML 字串。

const {prelude} = await prerenderToNodeStream(reactNode, options?)

注意事項

此 API 僅適用於 Node.js。使用 Web Streams 的環境,例如 Deno 和現代邊緣執行環境,應改用 prerender


參考

prerenderToNodeStream(reactNode, options?)

呼叫 prerenderToNodeStream 將您的應用程式渲染成靜態 HTML。

import { prerenderToNodeStream } from 'react-dom/static';

// The route handler syntax depends on your backend framework
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});

response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});

在客戶端上,呼叫 hydrateRoot 使伺服器產生的 HTML 具有互動性。

請參閱以下更多範例。

參數

  • reactNode:您要渲染成 HTML 的 React 節點。例如,像 <App /> 這樣的 JSX 節點。它應該代表整個文件,因此 App 元件應該渲染 <html> 標籤。

  • **選用** options:具有靜態生成選項的物件。

    • **選用** bootstrapScriptContent:如果指定,此字串將放在內聯 <script> 標籤中。
    • **選用** bootstrapScripts:要在頁面上輸出的 <script> 標籤的字串 URL 陣列。使用它來包含呼叫 hydrateRoot<script>。如果您根本不想在客戶端上運行 React,請省略它。
    • **選用** bootstrapModules:與 bootstrapScripts 相似,但改為輸出 <script type="module">
    • 選用 identifierPrefix:React 用於由 useId 產生的 ID 的字串前綴。可用於避免在同一頁面上使用多個根目錄時發生衝突。必須與傳遞給 hydrateRoot 的前綴相同。
    • 選用 namespaceURI:具有串流根 命名空間 URI 的字串。預設為一般 HTML。傳遞 'http://www.w3.org/2000/svg' 給 SVG 或 'http://www.w3.org/1998/Math/MathML' 給 MathML。
    • 選用 onError:一個在發生伺服器錯誤時觸發的回呼函式,無論是 可恢復的不可恢復的。預設情況下,這只會呼叫 console.error。如果您將其覆蓋為 記錄崩潰報告,請確保您仍然呼叫 console.error。您也可以使用它在發出 shell 之前 調整狀態碼
    • 選用 progressiveChunkSize:區塊中的位元組數。 閱讀更多關於預設啟發式演算法的資訊。
    • 選用 signal:一個 中止訊號,讓您可以 中止伺服器渲染 並在客戶端上渲染其餘部分。

回傳

prerenderToNodeStream 回傳一個 Promise

  • 如果渲染成功,Promise 將會解析為一個包含以下內容的物件:
    • prelude:一個 HTML 的 Node.js 串流。 您可以使用此串流分塊發送回應,或者您可以將整個串流讀入字串中。
  • 如果渲染失敗,Promise 將會被拒絕。 使用它來輸出一個備用的 shell。

注意事項

我應該什麼時候使用 prerenderToNodeStream

靜態 prerenderToNodeStream API 用於靜態伺服器端生成 (SSG)。與 renderToString 不同,prerenderToNodeStream 會等待所有資料載入後才解析。這使得它適用於生成完整頁面的靜態 HTML,包括需要使用 Suspense 提取的資料。要隨著載入流式傳輸內容,請使用串流伺服器端渲染 (SSR) API,例如 renderToReadableStream


用法

將 React 樹渲染為靜態 HTML 串流

呼叫 prerenderToNodeStream 將您的 React 樹渲染為靜態 HTML 到 Node.js 串流。

import { prerenderToNodeStream } from 'react-dom/static';

// The route handler syntax depends on your backend framework
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});

response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});

除了 根組件 之外,您還需要提供 引導程式 <script> 路徑 列表。您的根組件應該回傳**包含根 <html> 標籤的整個文件。**

例如,它可能看起來像這樣:

export default function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}

React 會將 文件類型 和您的 引導程式 <script> 標籤 注入到產生的 HTML 串流中。

<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>

在客戶端,您的啟動腳本應該使用 hydrateRoot 將整個 document 進行 hydration:

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App />);

這會將事件監聽器附加到靜態伺服器生成的 HTML 並使其具有互動性。

深入探討

從建置輸出讀取 CSS 和 JS 資源路徑

最終的資源網址(例如 JavaScript 和 CSS 檔案)通常在建置後會進行雜湊處理。例如,您可能會得到 styles.123456.css,而不是 styles.css。雜湊靜態資源檔名可確保相同資源的每個不同建置版本都具有不同的檔名。這很有用,因為它允許您安全地啟用靜態資源的長期快取:具有特定名稱的檔案內容永遠不會更改。

但是,如果您在建置完成後才知道資源網址,就無法將它們放入原始碼中。例如,像前面那樣將 "/styles.css" 硬編碼到 JSX 中將無法正常工作。為了將它們排除在原始碼之外,您的根組件可以從作為 prop 傳遞的地圖中讀取實際的檔名

export default function App({ assetMap }) {
return (
<html>
<head>
<title>My app</title>
<link rel="stylesheet" href={assetMap['styles.css']}></link>
</head>
...
</html>
);
}

在伺服器上,渲染 <App assetMap={assetMap} /> 並傳遞您的 assetMap 以及資源網址

// You'd need to get this JSON from your build tooling, e.g. read it from the build output.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};

app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: [assetMap['/main.js']]
});

response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});

由於您的伺服器現在正在渲染 <App assetMap={assetMap} />,因此您也需要在客戶端使用 assetMap 渲染它,以避免 hydration 錯誤。您可以序列化 assetMap 並將其傳遞給客戶端,如下所示

// You'd need to get this JSON from your build tooling.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};

app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
// Careful: It's safe to stringify() this because this data isn't user-generated.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});

response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});

在上面的範例中,bootstrapScriptContent 選項添加了一個額外的內聯 <script> 標籤,該標籤在客戶端上設置全域 window.assetMap 變數。這讓客戶端程式碼可以讀取相同的 assetMap

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App assetMap={window.assetMap} />);

客戶端和伺服器都使用相同的 assetMap prop 渲染 App,因此沒有 hydration 錯誤。


將 React 樹渲染為靜態 HTML 字串

呼叫 prerenderToNodeStream 將您的應用程式渲染為靜態 HTML 字串

import { prerenderToNodeStream } from 'react-dom/static';

async function renderToString() {
const {prelude} = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js']
});

return new Promise((resolve, reject) => {
let data = '';
prelude.on('data', chunk => {
data += chunk;
});
prelude.on('end', () => resolve(data));
prelude.on('error', reject);
});
}

這將產生 React 組件的初始非互動式 HTML 輸出。在客戶端,您需要呼叫 hydrateRoot 來對伺服器生成的 HTML 進行 *hydration* 並使其具有互動性。


等待所有資料載入

prerenderToNodeStream 會等待所有資料載入完畢,才會完成靜態 HTML 生成並解析。例如,考慮一個顯示封面、帶有朋友和照片的側邊欄以及貼文列表的個人資料頁面

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

想像一下 <Posts /> 需要載入一些資料,這需要一些時間。理想情況下,您希望等待貼文完成,以便將其包含在 HTML 中。為此,您可以使用 Suspense 暫停資料,prerenderToNodeStream 將等待暫停的內容完成,然後再解析為靜態 HTML。

注意事項

只有啟用 Suspense 的資料來源才會啟用 Suspense 組件。它們包括

  • 使用啟用 Suspense 的框架(如 RelayNext.js)進行資料擷取
  • 使用 lazy 延遲載入組件程式碼
  • 使用 use 讀取 Promise 的值

Suspense 不會偵測在 Effect 或事件處理程式內擷取資料的時間點。

在上面的 Posts 組件中載入資料的確切方式取決於您的框架。如果您使用啟用 Suspense 的框架,您會在其資料擷取文件中找到詳細資訊。

尚未支援在不使用特定框架的情況下啟用 Suspense 的資料擷取。實作啟用 Suspense 的資料來源的需求不穩定且未記錄。在 React 的未來版本中將會發布用於將資料來源與 Suspense 整合的官方 API。


疑難排解

我的串流要等到整個應用程式渲染完成後才會開始

`prerenderToNodeStream` 回應會等待整個應用程式完成渲染,包含等待所有 Suspense 邊界解析完成,才會解析。它設計用於預先產生靜態網站 (SSG),並不支援在載入時串流更多內容。

若要在載入時串流內容,請使用串流伺服器渲染 API,例如 renderToPipeableStream