renderToPipeableStream

renderToPipeableStream 将 React 树渲染为可管道 Node.js 流。

¥renderToPipeableStream renders a React tree to a pipeable Node.js Stream.

const { pipe, abort } = renderToPipeableStream(reactNode, options?)

注意

此 API 特定于 Node.js。具有 网络流, 的环境(如 Deno 和现代 Edge 运行时)应改用 renderToReadableStream

¥This API is specific to Node.js. Environments with Web Streams, like Deno and modern edge runtimes, should use renderToReadableStream instead.


参考

¥Reference

renderToPipeableStream(reactNode, options?)

调用 renderToPipeableStream 将你的 React 树作为 HTML 渲染到 Node.js 流。

¥Call renderToPipeableStream to render your React tree as HTML into a Node.js Stream.

import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

在客户端,调用 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 element 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 streaming 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.

    • 可选 nonce:允许 script-src Content-Security-Policy 脚本的 nonce 字符串。

      ¥optional nonce: A nonce string to allow scripts for script-src Content-Security-Policy.

    • 可选 onAllReady:当所有渲染完成时触发的回调,包括 shell 和所有额外的 内容。 你可以使用它而不是 onShellReady 用于爬虫和静态生成。 如果你在此处开始流式传输,你将不会获得任何渐进式加载。该流将包含最终的 HTML。

      ¥optional onAllReady: A callback that fires when all rendering is complete, including both the shell and all additional content. You can use this instead of onShellReady for crawlers and static generation. If you start streaming here, you won’t get any progressive loading. The stream will contain the final HTML.

    • 可选 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.

    • 可选 onShellReady:渲染 初始 shell 后立即触发的回调。你可以在此处 设置状态码 并致电 pipe 以开始直播。React 将在 shell 之后加上内联 <script> 标签,这些标签用内容替换 HTML 加载回退。

      ¥optional onShellReady: A callback that fires right after the initial shell has been rendered. You can set the status code and call pipe here to start streaming. React will stream the additional content after the shell along with the inline <script> tags that place that replace the HTML loading fallbacks with the content.

    • 可选 onShellError:如果在渲染初始 shell 时出错,则会触发回调。它接收错误作为参数。流中还没有发出任何字节,onShellReadyonAllReady 都不会被调用,所以你可以 输出回退 HTML shell。

      ¥optional onShellError: A callback that fires if there was an error rendering the initial shell. It receives the error as an argument. No bytes were emitted from the stream yet, and neither onShellReady nor onAllReady will get called, so you can output a fallback HTML shell.

    • 可选 progressiveChunkSize:块中的字节数。阅读有关默认启发式的更多信息。

      ¥optional progressiveChunkSize: The number of bytes in a chunk. Read more about the default heuristic.

返回

¥Returns

renderToPipeableStream 返回一个有两个方法的对象:

¥renderToPipeableStream returns an object with two methods:

  • pipe 将 HTML 输出到提供的 可写的 Node.js 流。 如果要启用流,则在 onShellReady 中调用 pipe,或者在 onAllReady 中用于爬虫和静态生成。

    ¥pipe outputs the HTML into the provided Writable Node.js Stream. Call pipe in onShellReady if you want to enable streaming, or in onAllReady for crawlers and static generation.

  • abort 让你 中止服务器渲染 并在客户端渲染其余部分。

    ¥abort lets you abort server rendering and render the rest on the client.


用法

¥Usage

将 React 树作为 HTML 渲染到 Node.js 流

¥Rendering a React tree as HTML to a Node.js Stream

调用 renderToPipeableStream 将你的 React 树作为 HTML 渲染到 Node.js 流

¥Call renderToPipeableStream to render your React tree as HTML into a Node.js Stream:

import { renderToPipeableStream } from 'react-dom/server';

// The route handler syntax depends on your backend framework
app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
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 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>
...
<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('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, {
bootstrapScripts: [assetMap['main.js']],
onShellReady() {
response.setHeader('content-type', 'text/html');
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('/', (request, response) => {
const { pipe } = renderToPipeableStream(<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']],
onShellReady() {
response.setHeader('content-type', 'text/html');
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.


在加载时流式传输更多内容

¥Streaming more content as it loads

流式传输允许用户甚至在所有数据加载到服务器之前就开始查看内容。例如,考虑一个显示封面、带有朋友和照片的边栏以及帖子列表的个人资料页面:

¥Streaming allows the user to start seeing the content even before all the data has loaded on the server. 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>
<Posts />
</ProfileLayout>
);
}

想象一下,加载 <Posts /> 的数据需要一些时间。理想情况下,你希望在不等待帖子的情况下向用户显示其余的个人资料页面内容。为此,Posts 封装在 <Suspense> 边界中:

¥Imagine that loading data for <Posts /> takes some time. Ideally, you’d want to show the rest of the profile page content to the user without waiting for the posts. To do this, wrap Posts in a <Suspense> boundary:

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

这告诉 React 在 Posts 加载数据之前开始流式传输 HTML。React 将首先发送用于加载回退 (PostsGlimmer) 的 HTML,然后,当 Posts 完成加载其数据时,React 将发送剩余的 HTML 以及用该 HTML 替换加载回退的内联 <script> 标记。从用户的角度来看,该页面将首先出现 PostsGlimmer,然后由 Posts 替换。

¥This tells React to start streaming the HTML before Posts loads its data. React will send the HTML for the loading fallback (PostsGlimmer) first, and then, when Posts finishes loading its data, React will send the remaining HTML along with an inline <script> tag that replaces the loading fallback with that HTML. From the user’s perspective, the page will first appear with the PostsGlimmer, later replaced by the Posts.

你可以进一步 嵌套 <Suspense> 边界 创建更细粒度的加载序列:

¥You can further nest <Suspense> boundaries to create a more granular loading sequence:

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

在此示例中,React 可以更早地开始流式传输页面。只有 ProfileLayoutProfileCover 必须先完成渲染,因为它们没有封装在任何 <Suspense> 边界中。但是,如果 SidebarFriendsPhotos 需要加载一些数据,React 将改为发送 BigSpinner 回退的 HTML。然后,随着更多数据可用,将继续显示更多内容,直到所有内容都可见为止。

¥In this example, React can start streaming the page even earlier. Only ProfileLayout and ProfileCover must finish rendering first because they are not wrapped in any <Suspense> boundary. However, if Sidebar, Friends, or Photos need to load some data, React will send the HTML for the BigSpinner fallback instead. Then, as more data becomes available, more content will continue to be revealed until all of it becomes visible.

Streaming 不需要等待 React 本身在浏览器中加载,也不需要等待你的应用变为交互式。在加载任何 <script> 标记之前,来自服务器的 HTML 内容将逐渐显示。

¥Streaming does not need to wait for React itself to load in the browser, or for your app to become interactive. The HTML content from the server will get progressively revealed before any of the <script> tags load.

详细了解流式 HTML 的工作原理。

¥Read more about how streaming HTML works.

注意

只有启用了 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.


指定进入 shell 的内容

¥Specifying what goes into the shell

你的应用在任何 <Suspense> 边界之外的部分称为外壳:

¥The part of your app outside of any <Suspense> boundaries is called the shell:

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

它决定了用户可能看到的最早加载状态:

¥It determines the earliest loading state that the user may see:

<ProfileLayout>
<ProfileCover />
<BigSpinner />
</ProfileLayout>

如果将整个应用封装到根的 <Suspense> 边界中,外壳将仅包含该加载控件。然而,这并不是一种令人愉快的用户体验,因为在屏幕上看到一个大的旋转器比多等一会儿看到真正的布局感觉更慢、更烦人。这就是为什么通常你会希望放置 <Suspense> 边界,使外壳感觉最小但完整 - 就像整个页面布局的骨架。

¥If you wrap the whole app into a <Suspense> boundary at the root, the shell will only contain that spinner. However, that’s not a pleasant user experience because seeing a big spinner on the screen can feel slower and more annoying than waiting a bit more and seeing the real layout. This is why usually you’ll want to place the <Suspense> boundaries so that the shell feels minimal but complete—like a skeleton of the entire page layout.

onShellReady 回调在整个 shell 渲染完成后触发。通常,你将开始流式传输:

¥The onShellReady callback fires when the entire shell has been rendered. Usually, you’ll start streaming then:

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

onShellReady 触发时,嵌套 <Suspense> 边界中的组件可能仍在加载数据。

¥By the time onShellReady fires, components in nested <Suspense> boundaries might still be loading data.


服务器上的日志记录崩溃

¥Logging crashes on the server

默认情况下,服务器上的所有错误都记录到控制台。你可以覆盖此行为以记录崩溃报告:

¥By default, all errors on the server are logged to console. You can override this behavior to log crash reports:

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

如果你提供自定义 onError 实现,请不要忘记像上面一样将错误记录到控制台。

¥If you provide a custom onError implementation, don’t forget to also log errors to the console like above.


从 shell 中的错误中恢复

¥Recovering from errors inside the shell

在此示例中,shell 包含 ProfileLayoutProfileCoverPostsGlimmer

¥In this example, the shell contains ProfileLayout, ProfileCover, and PostsGlimmer:

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

如果在渲染这些组件时发生错误,React 将不会向客户端发送任何有意义的 HTML。重写 onShellError 以发送不依赖服务器渲染的回退 HTML 作为最后的手段:

¥If an error occurs while rendering those components, React won’t have any meaningful HTML to send to the client. Override onShellError to send a fallback HTML that doesn’t rely on server rendering as the last resort:

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

如果在生成 shell 时出现错误,onErroronShellError 都会触发。使用 onError 进行错误报告,使用 onShellError 发送回退 HTML 文档。你的回退 HTML 不必是错误页面。而是,你可以包含一个仅在客户端渲染你的应用的替代 shell。

¥If there is an error while generating the shell, both onError and onShellError will fire. Use onError for error reporting and use onShellError to send the fallback HTML document. Your fallback HTML does not have to be an error page. Instead, you may include an alternative shell that renders your app on the client only.


从 shell 外部的错误中恢复

¥Recovering from errors outside the shell

在此示例中,<Posts /> 组件封装在 <Suspense> 中,因此它不是 shell 的一部分:

¥In this example, the <Posts /> component is wrapped in <Suspense> so it is not a part of the shell:

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

如果 Posts 组件或其中某处发生错误,React 将 尝试从中恢复:

¥If an error happens in the Posts component or somewhere inside it, React will try to recover from it:

  1. 它会将最近的 <Suspense> 边界 (PostsGlimmer) 的加载回退发送到 HTML 中。

    ¥It will emit the loading fallback for the closest <Suspense> boundary (PostsGlimmer) into the HTML.

  2. 它将 “放弃” 尝试在服务器上渲染 Posts 内容。

    ¥It will “give up” on trying to render the Posts content on the server anymore.

  3. 当 JavaScript 代码加载到客户端时,React 将重试在客户端上渲染 Posts

    ¥When the JavaScript code loads on the client, React will retry rendering Posts on the client.

如果在客户端重试渲染 Posts 也失败,React 将在客户端抛出错误。与渲染期间抛出的所有错误一样,最近的父错误边界 决定如何向用户渲染错误。实际上,这意味着用户将看到加载指示器,直到确定错误不可恢复。

¥If retrying rendering Posts on the client also fails, React will throw the error on the client. As with all the errors thrown during rendering, the closest parent error boundary determines how to present the error to the user. In practice, this means that the user will see a loading indicator until it is certain that the error is not recoverable.

如果在客户端重试渲染 Posts 成功,来自服务器的加载回退将替换为客户端渲染输出。用户不会知道出现了服务器错误。但是,服务器 onError 回调和客户端 onRecoverableError 回调将触发,以便你可以收到有关错误的通知。

¥If retrying rendering Posts on the client succeeds, the loading fallback from the server will be replaced with the client rendering output. The user will not know that there was a server error. However, the server onError callback and the client onRecoverableError callbacks will fire so that you can get notified about the error.


设置状态码

¥Setting the status code

流式传输引入了权衡。你希望尽早开始流式传输页面,以便用户可以更快地看到内容。但是,一旦开始流式传输,就无法再设置响应状态代码。

¥Streaming introduces a tradeoff. You want to start streaming the page as early as possible so that the user can see the content sooner. However, once you start streaming, you can no longer set the response status code.

通过 划分你的应用 进入 shell(在所有 <Suspense> 边界之上)和其余内容,你已经解决了这个问题的一部分。如果 shell 出错,你将获得 onShellError 回调,它允许你设置错误状态代码。否则,你知道应用可能会在客户端恢复,因此你可以发送 “OK”。

¥By dividing your app into the shell (above all <Suspense> boundaries) and the rest of the content, you’ve already solved a part of this problem. If the shell errors, you’ll get the onShellError callback which lets you set the error status code. Otherwise, you know that the app may recover on the client, so you can send “OK”.

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

如果外壳外的组件(即 <Suspense> 边界内)抛出错误,React 不会停止渲染。这意味着 onError 回调将触发,但你仍然会得到 onShellReady 而不是 onShellError。这是因为 React 将尝试从客户端 如上所述。 上的错误中恢复

¥If a component outside the shell (i.e. inside a <Suspense> boundary) throws an error, React will not stop rendering. This means that the onError callback will fire, but you will still get onShellReady instead of onShellError. This is because React will try to recover from that error on the client, as described above.

但是,如果你愿意,你可以使用出现错误的事实来设置状态代码:

¥However, if you’d like, you can use the fact that something has errored to set the status code:

let didError = false;

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});

这只会捕获在生成初始 shell 内容时发生的 shell 外部错误,因此它并不详尽。如果了解某些内容是否发生错误很重要,你可以将其上移到 shell 中。

¥This will only catch errors outside the shell that happened while generating the initial shell content, so it’s not exhaustive. If knowing whether an error occurred for some content is critical, you can move it up into the shell.


以不同的方式处理不同的错误

¥Handling different errors in different ways

你可以使用 创建你自己的 Error 子类 并使用 instanceof 运算符来检查抛出的错误。例如,你可以定义一个自定义的 NotFoundError 并将其从你的组件中抛出。然后,你的 onErroronShellReadyonShellError 回调可以根据错误类型执行不同的操作:

¥You can create your own Error subclasses and use the instanceof operator to check which error is thrown. For example, you can define a custom NotFoundError and throw it from your component. Then your onError, onShellReady, and onShellError callbacks can do something different depending on the error type:

let didError = false;
let caughtError = null;

function getStatusCode() {
if (didError) {
if (caughtError instanceof NotFoundError) {
return 404;
} else {
return 500;
}
} else {
return 200;
}
}

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});

请记住,一旦发出 shell 并开始流式传输,就无法更改状态代码。

¥Keep in mind that once you emit the shell and start streaming, you can’t change the status code.


等待为爬虫和静态生成加载所有内容

¥Waiting for all content to load for crawlers and static generation

流式提供了更好的用户体验,因为用户可以在内容可用时看到内容。

¥Streaming offers a better user experience because the user can see the content as it becomes available.

但是,当爬虫访问你的页面时,或者如果你在构建时生成页面,你可能希望首先加载所有内容,然后生成最终的 HTML 输出,而不是逐步显示它。

¥However, when a crawler visits your page, or if you’re generating the pages at the build time, you might want to let all of the content load first and then produce the final HTML output instead of revealing it progressively.

你可以使用 onAllReady 回调等待所有内容加载:

¥You can wait for all the content to load using the onAllReady callback:

let didError = false;
let isCrawler = // ... depends on your bot detection strategy ...

const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
if (!isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onAllReady() {
if (isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});

普通访问者将获得逐步加载的内容流。爬虫将在所有数据加载后接收最终的 HTML 输出。然而,这也意味着爬虫将不得不等待所有数据,其中一些可能加载缓慢或出错。根据你的应用,你也可以选择将 shell 发送给爬虫。

¥A regular visitor will get a stream of progressively loaded content. A crawler will receive the final HTML output after all the data loads. However, this also means that the crawler will have to wait for all data, some of which might be slow to load or error. Depending on your app, you could choose to send the shell to the crawlers too.


中止服务器渲染

¥Aborting server rendering

你可以在超时后强制服务器渲染到 “放弃”:

¥You can force the server rendering to “give up” after a timeout:

const { pipe, abort } = renderToPipeableStream(<App />, {
// ...
});

setTimeout(() => {
abort();
}, 10000);

React 会将剩余的加载回退作为 HTML 刷新,并将尝试在客户端渲染其余部分。

¥React will flush the remaining loading fallbacks as HTML, and will attempt to render the rest on the client.


React 中文网 - 粤ICP备13048890号