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。对于 SVG,请传递'http://www.w3.org/2000/svg'
;对于 MathML,请传递'http://www.w3.org/1998/Math/MathML'
。 - 可选
onError
:当出现服务器错误时触发的回调函数,无论错误是否 可恢复 或 不可恢复。默认情况下,这只会调用console.error
。如果将其覆盖为 记录崩溃报告,请确保仍然调用console.error
。您也可以使用它来 调整状态码,然后再发出 shell。 - 可选
progressiveChunkSize
:块中的字节数。阅读更多关于默认启发式的信息。 - 可选
signal
:一个 中止信号,允许您 中止服务器渲染 并在客户端渲染其余部分。
- 可选
返回
prerenderToNodeStream
返回一个 Promise
- 如果渲染成功,Promise 将解析为一个包含以下内容的对象:
prelude
:一个 Node.js 流,包含 HTML。您可以使用此流分块发送响应,或者可以将整个流读取到字符串中。
- 如果渲染失败,Promise 将被拒绝。使用此方法输出备用 shell。
用法
将 React 树渲染到静态 HTML 流中
调用 prerenderToNodeStream
将您的 React 树渲染到 Node.js 流 中的静态 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);
});
除了 根组件 之外,您还需要提供一个 bootstrap <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 将将 doctype 和您的 bootstrap <script>
标签 注入到生成的 HTML 流中。
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>
在客户端,您的 bootstrap 脚本应该 使用对 hydrateRoot
的调用来 hydrating-an-entire-document
整个 document
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
这将把事件监听器附加到静态服务器生成的 HTML,并使其具有交互性。
深入探讨
最终的资源 URL(例如 JavaScript 和 CSS 文件)在构建后通常会被哈希化。例如,` styles.css
`可能会变成` styles.123456.css
`。对静态资源文件名进行哈希化可以保证相同资源的不同构建版本具有不同的文件名。这很有用,因为它允许你安全地启用静态资源的长期缓存:具有特定名称的文件永远不会更改内容。
但是,如果你在构建后才知道资源 URL,那么你就无法将它们放在源代码中。例如,像前面那样将` "/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
`与资源 URL 一起传递。
// 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
`进行渲染,以避免水合错误。你可以像这样序列化并传递` 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
`,因此不会出现水合错误。
将 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 并使其具有交互性。
等待所有数据加载
` 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。