prerenderToNodeStream

prerender 使用 Node.js 流 将React树渲染到静态HTML字符串。

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

注意

此API特定于Node.js。具有 Web Streams 的环境(如Deno和现代边缘运行时)应该使用 prerender


参考

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。

注意

何时使用 prerenderToNodeStream

静态 prerenderToNodeStream API 用于静态服务器端生成 (SSG)。与 renderToString 不同,prerenderToNodeStream 会等待所有数据加载完毕后再解析。这使得它适合生成完整页面的静态 HTML,包括需要使用 Suspense 获取的数据。要随着数据的加载而流式传输内容,请使用流式服务器端渲染 (SSR) API,例如 renderToReadableStream


用法

将 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,并使其具有交互性。

深入探讨

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

最终的资源 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 之前等待挂起的內容完成。

注意

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

  • 使用启用 Suspense 的框架(如 RelayNext.js)进行数据获取
  • 使用 lazy 延迟加载组件代码
  • 使用 use 读取 Promise 的值

Suspense **不会** 检测在 Effect 或事件处理程序中获取的数据。

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

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


故障排除

我的流在整个应用渲染完成之前不会启动

` prerenderToNodeStream `响应会在整个应用渲染完成(包括等待所有 suspense 边界解析)之后再解析。它专为提前进行静态站点生成 (SSG) 而设计,不支持在加载时流式传输更多内容。

要在加载时流式传输内容,请使用流式服务器渲染 API,例如 renderToPipeableStream