prerenderToNodeStream
prerenderToNodeStream преобразует дерево React в статическую HTML-строку с помощью потока Node.js..
const {prelude, postponed} = 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: узел React, который нужно преобразовать в HTML. Например, узел JSX, такой как<App />. Предполагается, что он представляет весь документ, поэтому компонент App должен выводить тег<html>. -
необязательный
options: объект с параметрами статической генерации.-
необязательный
bootstrapScriptContent: если указан, эта строка будет помещена во встроенный тег<script>. -
необязательный
bootstrapScripts: массив URL-адресов в виде строк для тегов<script>, которые нужно добавить на страницу. Используйте его, чтобы добавить<script>, вызывающийhydrateRoot. Не указывайте его, если вы вообще не хотите запускать React на клиенте. -
необязательный
bootstrapModules: аналогbootstrapScripts, но вместо этого добавляет<script type="module">. -
необязательный
identifierPrefix: строковый префикс, который React использует для идентификаторов, созданных с помощьюuseId. Полезен для предотвращения конфликтов при использовании нескольких корней на одной странице. Должен совпадать с префиксом, переданным вhydrateRoot. -
необязательный
namespaceURI: строка с URI пространства имен корня потока. По умолчанию используется обычный HTML. Передайте'http://www.w3.org/2000/svg'для SVG или'http://www.w3.org/1998/Math/MathML'для MathML. -
необязательный
onError: функция обратного вызова, вызываемая при любой серверной ошибке — как восстановимой, так и невосстановимой. По умолчанию она вызывает толькоconsole.error. Если вы переопределите её для регистрации отчётов о сбоях, не забудьте по-прежнему вызыватьconsole.error. Также с её помощью можно изменить код состояния до отправки оболочки. -
необязательный
progressiveChunkSize: количество байтов в одном фрагменте. Подробнее о стандартной эвристике. -
необязательный
signal: сигнал прерывания, позволяющий прервать предварительную генерацию и отрисовать остальную часть на клиенте.
-
необязательный
Возвращаемое значение
prerenderToNodeStream возвращает Promise:
- Если отрисовка выполнена успешно, Promise разрешается объектом, содержащим:
-
prelude: поток Node.js. с HTML. Этот поток можно использовать для отправки ответа частями или прочитать целиком в строку. -
postponed: непрозрачный объект, который можно сериализовать в JSON и передать вresumeToPipeableStream, еслиprerenderToNodeStreamне завершился. В противном случае —null, указывающий, чтоpreludeсодержит всё содержимое и возобновление не требуется.
-
- Если отрисовка завершается с ошибкой, Promise отклоняется. Используйте это, чтобы вывести резервную оболочку.
Ограничения
nonce недоступен при предварительной генерации. Nonce должен быть уникальным для каждого запроса. Если вы используете nonce для защиты приложения с помощью CSP, включать его значение непосредственно в результат предварительной генерации было бы неуместно и небезопасно.
Примечание
Когда следует использовать prerenderToNodeStream?
Статический API prerenderToNodeStream используется для статической генерации на стороне сервера (SSG). В отличие от renderToString, prerenderToNodeStream ожидает загрузки всех данных, прежде чем завершить работу. Поэтому он подходит для создания статического HTML полной страницы, включая данные, которые нужно получить с помощью Suspense. Чтобы передавать содержимое по мере его загрузки, используйте потоковый API серверной отрисовки (SSR), например renderToReadableStream.
Выполнение prerenderToNodeStream можно прервать, а затем продолжить с помощью resumeToPipeableStream для поддержки частичной предварительной генерации.
Использование
Преобразование дерева React в поток статического HTML
Вызовите prerenderToNodeStream, чтобы преобразовать дерево React в статический HTML в поток Node.js:
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-скрипт должен гидратировать весь документ document с помощью вызова hydrateRoot:
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-адреса ресурсов становятся известны только после сборки, добавить их в исходный код невозможно. Например, жёстко заданное в JSX значение "/styles.css", как в предыдущем примере, не сработает. Чтобы не включать их в исходный код, корневой компонент может считывать настоящие имена файлов из карты, переданной через props:
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} />);
И клиент, и сервер отрисовывают App с одинаковым параметром assetMap, поэтому ошибок гидратации не возникает.
Преобразование дерева 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);
});
} В результате будет создан исходный неинтерактивный HTML-код компонентов React. На клиенте необходимо вызвать 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, таких как Relay и Next.js
- Отложенная загрузка кода компонентов с помощью
lazy - Чтение значения Promise с помощью
use
Suspense не обнаруживает загрузку данных внутри эффекта или обработчика события.
Точный способ загрузки данных в компоненте Posts зависит от используемого фреймворка. Если вы используете фреймворк с поддержкой Suspense, подробности можно найти в документации по загрузке данных для этого фреймворка.
Загрузка данных с поддержкой Suspense без использования фреймворка с заданными правилами пока не поддерживается. Требования к реализации источника данных с поддержкой Suspense нестабильны и не документированы. Официальный API для интеграции источников данных с Suspense появится в будущей версии React.
Прерывание предварительной генерации
Можно принудительно остановить предварительную генерацию по истечении заданного времени ожидания:
async function renderToString() {
const controller = new AbortController();
setTimeout(() => {
controller.abort()
}, 10000);
try {
// the prelude will contain all the HTML that was prerendered
// before the controller aborted.
const {prelude} = await prerenderToNodeStream(<App />, {
signal: controller.signal,
});
//... Все границы Suspense с незавершёнными дочерними элементами будут включены в прелюдию в резервном состоянии.
Это можно использовать для частичной предварительной генерации вместе с resumeToPipeableStream или resumeAndPrerenderToNodeStream.
Устранение неполадок
Поток не начинает передавать данные, пока не отрисовано всё приложение
Ответ prerenderToNodeStream ожидает завершения отрисовки всего приложения, в том числе разрешения всех границ Suspense, прежде чем завершиться. Он предназначен для предварительной генерации статических сайтов (SSG) и не поддерживает передачу дополнительного содержимого по мере его загрузки.
Чтобы передавать содержимое по мере его загрузки, используйте потоковый API серверной отрисовки, например renderToPipeableStream.
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://react.dev/reference/react-dom/static/prerenderToNodeStream