prerender 使用 Web 流 将 React 树渲染为静态 HTML 字符串。

¥prerender renders a React tree to a static HTML string using a Web Stream.

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

注意

此 API 依赖于 网络流。 对于 Node.js,请改用 prerenderToNodeStream

¥This API depends on Web Streams. For Node.js, use prerenderToNodeStream instead.


参考

¥Reference

prerender(reactNode, options?)

调用 prerender 将你的应用渲染为静态 HTML。

¥Call prerender to render your app to static HTML.

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

async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}

在客户端,调用 hydrateRoot 使服务器生成的 HTML 具有交互性。

¥On the client, call hydrateRoot to make the server-generated HTML interactive.

请参阅下面的更多示例。

¥See more examples below.

参数

¥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 calls hydrateRoot. Omit it if you don’t want to run React on the client at all.

    • 可选 bootstrapModules:像 bootstrapScripts,但发出 <script type="module">

      ¥optional bootstrapModules: Like bootstrapScripts, but emits <script type="module"> instead.

    • 可选 identifierPrefix:React 用于 useId 生成的 ID 的字符串前缀 有助于避免在同一页面上使用多个根时发生冲突。必须与传递给 hydrateRoot 的前缀相同

      ¥optional identifierPrefix: A string prefix React uses for IDs generated by useId. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed to hydrateRoot.

    • 可选 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 calls console.error. If you override it to log crash reports, make sure that you still call console.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

prerender 返回一个 Promise:

¥prerender returns a Promise:

  • 如果渲染成功,则 Promise 将解析为包含以下内容的对象:

    ¥If rendering the is successful, the Promise will resolve to an object containing:

    • preludeWeb 流 的 HTML。你可以使用此流分块发送响应,也可以将整个流读入字符串。

      ¥prelude: a Web 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.

注意

我什么时候应该使用 prerender

¥When should I use prerender?

静态 prerender API 用于静态服务器端生成 (SSG)。与 renderToString 不同,prerender 等待所有数据加载后再解析。这使其适合为整个页面生成静态 HTML,包括需要使用 Suspense 获取的数据。要在加载时流式传输内容,请使用流式服务器端渲染 (SSR) API,如 renderToReadableStream

¥The static prerender API is used for static server-side generation (SSG). Unlike renderToString, prerender waits for all data to load before resolving. This makes it suitable for generating static HTML for a full page, including data that needs to be fetched using Suspense. To stream content as it loads, use a streaming server-side render (SSR) API like renderToReadableStream.


用法

¥Usage

将 React 树渲染为静态 HTML 流

¥Rendering a React tree to a stream of static HTML

调用 prerender 将你的 React 树渲染为静态 HTML 进入 可读网络流:

¥Call prerender to render your React tree to static HTML into a Readable Web Stream::

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

async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}

除了 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.

深入研究

从构建输出中读取 CSS 和 JS 资源路径

¥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'
};

async function handler(request) {
const {prelude} = await prerender(<App assetMap={assetMap} />, {
bootstrapScripts: [assetMap['/main.js']]
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}

由于你的服务器现在正在渲染 <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'
};

async function handler(request) {
const {prelude} = await prerender(<App assetMap={assetMap} />, {
// Careful: It's safe to stringify() this because this data isn't user-generated.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}

在上面的示例中,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

调用 prerender 将你的应用渲染为静态 HTML 字符串:

¥Call prerender to render your app to a static HTML string:

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

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

const reader = stream.getReader();
let content = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
return content;
}
content += Buffer.from(value).toString('utf8');
}
}

这将生成 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

prerender 等待所有数据加载后再完成静态 HTML 生成和解析。例如,考虑一个显示封面、带有朋友和照片的边栏以及帖子列表的个人资料页面:

¥prerender 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 暂停数据,prerender 将等待暂停的内容完成后再解析为静态 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 prerender will wait for the suspended content to finish before resolving to the static HTML.

注意

只有启用了 Suspense 的数据源才会激活 Suspense 组件。它们包括:

¥Only Suspense-enabled data sources will activate the Suspense component. They include:

  • 使用支持 Suspense 的框架(如 RelayNext.js)获取数据

    ¥Data fetching with Suspense-enabled frameworks like Relay and Next.js

  • 使用 lazy 延迟加载组件代码

    ¥Lazy-loading component code with lazy

  • 使用 use 读取 Promise 的值

    ¥Reading the value of a Promise with use

Suspense 不会检测何时在副作用或事件处理程序中获取数据。

¥Suspense does not detect when data is fetched inside an Effect or event handler.

在上面的 Posts 组件中加载数据的确切方式取决于你的框架。如果你使用支持 Suspense 的框架,你将在其数据获取文档中找到详细信息。

¥The exact way you would load data in the Posts component above depends on your framework. If you use a Suspense-enabled framework, you’ll find the details in its data fetching documentation.

尚不支持在不使用固定框架的情况下启用 Suspense 的数据获取。实现启用 Suspense 的数据源的要求不稳定且未记录。用于将数据源与 Suspense 集成的官方 API 将在 React 的未来版本中发布。

¥Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React.


故障排除

¥Troubleshooting

直到整个应用渲染完成,我的流才会启动

¥My stream doesn’t start until the entire app is rendered

prerender 响应等待整个应用完成渲染,包括等待所有 suspense 边界解析后再解析。它是为预先生成静态站点 (SSG) 而设计的,不支持在加载时流式传输更多内容。

¥The prerender 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,如 renderToReadableStream

¥To stream content as it loads, use a streaming server render API like renderToReadableStream.


React 中文网 - 粤ICP备13048890号