prerender
使用 Node.js 流。 将 React 树渲染为静态 HTML 字符串。
¥prerender
renders a React tree to a static HTML string using a Node.js Stream..
const {prelude} = await prerenderToNodeStream(reactNode, options?)
参考
¥Reference
prerenderToNodeStream(reactNode, options?)
调用 prerenderToNodeStream
将你的应用渲染为静态 HTML。
¥Call prerenderToNodeStream
to render your app to static 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 具有交互性。
¥On the client, call hydrateRoot
to make the server-generated HTML interactive.
参数
¥Parameters
-
reactNode
:要渲染为 HTML 的 React 节点。例如,像<App />
这样的 JSX 节点。它应该表示整个文档,因此 App 组件应该渲染<html>
标签。¥
reactNode
: A React node you want to render to HTML. For example, a JSX node like<App />
. It is expected to represent the entire document, so the App component should render the<html>
tag. -
可选
options
:具有静态生成选项的对象。¥optional
options
: An object with static generation options.-
可选
bootstrapScriptContent
:如果指定,此字符串将放置在内联<script>
标记中。¥optional
bootstrapScriptContent
: If specified, this string will be placed in an inline<script>
tag. -
可选
bootstrapScripts
:要在页面上发出的<script>
标记的字符串 URL 数组。使用它来包含调用hydrateRoot
。 的<script>
如果你根本不想在客户端上运行 React,请忽略它。¥optional
bootstrapScripts
: An array of string URLs for the<script>
tags to emit on the page. Use this to include the<script>
that callshydrateRoot
. Omit it if you don’t want to run React on the client at all. -
可选
bootstrapModules
:像bootstrapScripts
,但发出<script type="module">
。¥optional
bootstrapModules
: LikebootstrapScripts
, but emits<script type="module">
instead. -
可选
identifierPrefix
:React 用于useId
。 生成的 ID 的字符串前缀 有助于避免在同一页面上使用多个根时发生冲突。必须与传递给hydrateRoot
。 的前缀相同¥optional
identifierPrefix
: A string prefix React uses for IDs generated byuseId
. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed tohydrateRoot
. -
可选
namespaceURI
:流的根为 名称空间 URI 的字符串。默认为常规 HTML。为 SVG 传递'http://www.w3.org/2000/svg'
或为 MathML 传递'http://www.w3.org/1998/Math/MathML'
。¥optional
namespaceURI
: A string with the root namespace URI for the stream. Defaults to regular HTML. Pass'http://www.w3.org/2000/svg'
for SVG or'http://www.w3.org/1998/Math/MathML'
for MathML. -
可选
onError
:每当出现服务器错误时都会触发的回调,无论是 recoverable 还是 不。 默认情况下,这只会调用console.error
。如果你将其改写为 记录崩溃报告,,请确保你仍然调用console.error
。你也可以在触发 shell 之前将它用于 调整状态码。¥optional
onError
: A callback that fires whenever there is a server error, whether recoverable or not. By default, this only callsconsole.error
. If you override it to log crash reports, make sure that you still callconsole.error
. You can also use it to adjust the status code before the shell is emitted. -
可选
progressiveChunkSize
:块中的字节数。阅读有关默认启发式的更多信息。¥optional
progressiveChunkSize
: The number of bytes in a chunk. Read more about the default heuristic. -
可选
signal
:一个 中止信号,它允许你 中止服务器渲染 并在客户端上渲染其余部分。¥optional
signal
: An abort signal that lets you abort server rendering and render the rest on the client.
-
返回
¥Returns
prerenderToNodeStream
返回一个 Promise:
¥prerenderToNodeStream
returns a Promise:
-
如果渲染成功,则 Promise 将解析为包含以下内容的对象:
¥If rendering the is successful, the Promise will resolve to an object containing:
-
prelude
:Node.js 流。 的 HTML。你可以使用此流分块发送响应,也可以将整个流读入字符串。¥
prelude
: a Node.js Stream. of HTML. You can use this stream to send a response in chunks, or you can read the entire stream into a string.
-
-
如果渲染失败,则 Promise 将被拒绝。使用它来输出回退 shell。
¥If rendering fails, the Promise will be rejected. Use this to output a fallback shell.
用法
¥Usage
将 React 树渲染为静态 HTML 流
¥Rendering a React tree to a stream of static HTML
调用 prerenderToNodeStream
将你的 React 树渲染为静态 HTML 进入 Node.js 流。:
¥Call prerenderToNodeStream
to render your React tree to static HTML into a Node.js Stream.:
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);
});
除了 root component,你还需要提供 bootstrap <script>
paths 的列表。你的根组件应返回整个文档,包括根 <html>
标记。
¥Along with the root component, you need to provide a list of bootstrap <script>
paths. Your root component should return the entire document including the root <html>
tag.
例如,它可能看起来像这样:
¥For example, it might look like this:
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 流中:
¥React will inject the doctype and your bootstrap <script>
tags into the resulting HTML stream:
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>
在客户端,你的引导脚本应该是 通过调用 hydrateRoot
来补充整个 document
:
¥On the client, your bootstrap script should hydrate the entire document
with a call to hydrateRoot
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
这会将事件监听器附加到静态服务器生成的 HTML 并使其具有交互性。
¥This will attach event listeners to the static server-generated HTML and make it interactive.
深入研究
¥Reading CSS and JS asset paths from the build output
最终资源 URL(如 JavaScript 和 CSS 文件)通常在构建后进行哈希处理。例如,你可能最终得到的不是 styles.css
,而是 styles.123456.css
。散列静态资源文件名可确保同一资源的每个不同构建都将具有不同的文件名。这很有用,因为它可以让你安全地为静态资源启用长期缓存:具有特定名称的文件永远不会更改内容。
¥The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of styles.css
you might end up with styles.123456.css
. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content.
但是,如果你直到构建之后才知道资源 URL,则无法将它们放入源代码中。例如,像之前那样将 "/styles.css"
硬编码到 JSX 中是行不通的。为了让它们远离你的源代码,你的根组件可以从作为属性传递的映射中读取真实文件名:
¥However, if you don’t know the asset URLs until after the build, there’s no way for you to put them in the source code. For example, hardcoding "/styles.css"
into JSX like earlier wouldn’t work. To keep them out of your source code, your root component can read the real filenames from a map passed as a 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} />
并通过资源 URL 传递你的 assetMap
:
¥On the server, render <App assetMap={assetMap} />
and pass your assetMap
with the asset URLs:
// 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
并将其传递给客户端:
¥Since your server is now rendering <App assetMap={assetMap} />
, you need to render it with assetMap
on the client too to avoid hydration errors. You can serialize and pass assetMap
to the client like this:
// 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
:
¥In the example above, the bootstrapScriptContent
option adds an extra inline <script>
tag that sets the global window.assetMap
variable on the client. This lets the client code read the same assetMap
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);
客户端和服务器都使用相同的 assetMap
属性渲染 App
,因此没有水合作用错误。
¥Both client and server render App
with the same assetMap
prop, so there are no hydration errors.
将 React 树渲染为静态 HTML 字符串
¥Rendering a React tree to a string of static HTML
调用 prerenderToNodeStream
将你的应用渲染为静态 HTML 字符串:
¥Call prerenderToNodeStream
to render your app to a static HTML string:
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 并使其具有交互性。
¥This will produce the initial non-interactive HTML output of your React components. On the client, you will need to call hydrateRoot
to hydrate that server-generated HTML and make it interactive.
等待所有数据加载
¥Waiting for all data to load
prerenderToNodeStream
等待所有数据加载后再完成静态 HTML 生成和解析。例如,考虑一个显示封面、带有朋友和照片的边栏以及帖子列表的个人资料页面:
¥prerenderToNodeStream
waits for all data to load before finishing the static HTML generation and resolving. For example, consider a profile page that shows a cover, a sidebar with friends and photos, and a list of posts:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
想象一下,<Posts />
需要加载一些数据,这需要一些时间。理想情况下,你需要等待帖子完成,以便将其包含在 HTML 中。为此,你可以使用 Suspense 暂停数据,prerenderToNodeStream
将等待暂停的内容完成后再解析为静态 HTML。
¥Imagine that <Posts />
needs to load some data, which takes some time. Ideally, you’d want wait for the posts to finish so it’s included in the HTML. To do this, you can use Suspense to suspend on the data, and prerenderToNodeStream
will wait for the suspended content to finish before resolving to the static HTML.
故障排除
¥Troubleshooting
直到整个应用渲染完成,我的流才会启动
¥My stream doesn’t start until the entire app is rendered
prerenderToNodeStream
响应等待整个应用完成渲染,包括等待所有 suspense 边界解析后再解析。它是为预先生成静态站点 (SSG) 而设计的,不支持在加载时流式传输更多内容。
¥The prerenderToNodeStream
response waits for the entire app to finish rendering, including waiting for all suspense boundaries to resolve, before resolving. It is designed for static site generation (SSG) ahead of time and does not support streaming more content as it loads.
要在加载内容时进行流式传输,请使用流式服务器渲染 API,如 renderToPipeableStream。
¥To stream content as it loads, use a streaming server render API like renderToPipeableStream.