Spec-Zone.ru › React

prerenderToNodeStream

prerenderToNodeStream преобразует дерево React в статическую HTML-строку с помощью потока Node.js..

const {prelude, postponed} = await prerenderToNodeStream(reactNode, options?)
  • Справочник
    • prerenderToNodeStream(reactNode, options?)
  • Использование
    • Преобразование дерева React в поток статического HTML
    • Преобразование дерева React в строку статического HTML
    • Ожидание загрузки всех данных
    • Прерывание предварительной генерации
  • Устранение неполадок
    • Поток не начинает передавать данные, пока не отрисовано всё приложение

Примечание

Этот 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API