prerender
使用 Node.js 串流 將 React 樹狀結構渲染成靜態 HTML 字串。
const {prelude} = await prerenderToNodeStream(reactNode, options?)
參考
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。
用法
將 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 並使其具有互動性。
深入探討
最終的資源網址(例如 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。
疑難排解
我的串流要等到整個應用程式渲染完成後才會開始
`prerenderToNodeStream` 回應會等待整個應用程式完成渲染,包含等待所有 Suspense 邊界解析完成,才會解析。它設計用於預先產生靜態網站 (SSG),並不支援在載入時串流更多內容。
若要在載入時串流內容,請使用串流伺服器渲染 API,例如 renderToPipeableStream。