Spec-Zone.ru › React

prerender

prerender преобразует дерево React в статическую HTML-строку с помощью потока Web Streams.

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

Примечание

Этот API использует Web Streams. Для Node.js используйте вместо него prerenderToNodeStream.


Справочник

prerender(reactNode, options?)

Вызовите prerender, чтобы преобразовать приложение в статический HTML.

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

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

На клиенте вызовите 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: сигнал прерывания, который позволяет прервать предварительный рендеринг и отрисовать оставшуюся часть на клиенте.

Возвращаемое значение

prerender возвращает Promise:

  • Если рендеринг выполнен успешно, Promise будет разрешён объектом, содержащим:
    • prelude: поток Web Streams с HTML. Этот поток можно использовать для отправки ответа фрагментами либо прочитать его целиком в строку.
    • postponed: сериализуемый в JSON непрозрачный объект, который можно передать в resume, если prerender не завершился. В противном случае — null, означающий, что prelude содержит всё содержимое и продолжение не требуется.
  • Если рендеринг завершится ошибкой, Promise будет отклонён. Используйте это, чтобы вывести резервную оболочку.

Ограничения

nonce нельзя использовать как параметр при предварительном рендеринге. Значения nonce должны быть уникальными для каждого запроса. Если вы используете nonce для защиты приложения с помощью CSP, включать значение nonce в результат предварительного рендеринга было бы неправильно и небезопасно.

Примечание

Когда следует использовать prerender?

Статический API prerender используется для статической генерации на стороне сервера (SSG). В отличие от renderToString, prerender ожидает загрузки всех данных, прежде чем завершить работу. Поэтому он подходит для создания статического HTML полной страницы, включая данные, которые нужно получить с помощью Suspense. Чтобы передавать содержимое по мере его загрузки, используйте потоковый API серверного рендеринга (SSR), например renderToReadableStream.

prerender можно прервать, а затем продолжить с помощью resumeAndPrerender или возобновить с помощью resume для поддержки частичного предварительного рендеринга.


Использование

Преобразование дерева React в поток статического HTML

Вызовите prerender, чтобы преобразовать дерево React в статический HTML, записав результат в поток Web ReadableStream::

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' },
  });
}

Помимо корневого компонента, необходимо передать список путей к 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 ресурсов становятся известны только после сборки, их нельзя указать в исходном коде. Например, жёстко заданный "/styles.css" в JSX, как выше, работать не будет. Чтобы не хранить их в исходном коде, корневой компонент может считывать реальные имена файлов из карты, переданной в качестве пропса:

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

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 клиенту следующим образом:

// 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:

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App assetMap={window.assetMap} />);

И клиент, и сервер отрисовывают App с одним и тем же пропсом assetMap, поэтому ошибок гидратации не возникает.


Преобразование дерева React в строку статического HTML

Вызовите prerender, чтобы преобразовать приложение в строку статического HTML:

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

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

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

В результате будет создан исходный неинтерактивный HTML-код компонентов React. На клиенте необходимо вызвать hydrateRoot, чтобы гидратировать этот HTML, сгенерированный на сервере, и сделать его интерактивным.


Ожидание загрузки всех данных

prerender дожидается загрузки всех данных, прежде чем завершить генерацию статического HTML и разрешиться. Например, представьте страницу профиля, на которой отображаются обложка, боковая панель с друзьями и фотографиями, а также список публикаций:

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

Представим, что для <Posts /> необходимо загрузить данные, и это занимает некоторое время. В идеале нужно дождаться загрузки публикаций, чтобы включить их в HTML. Для этого можно использовать Suspense, чтобы приостановить выполнение до получения данных; prerender дождётся завершения загрузки приостановленного содержимого, прежде чем вернуть статический HTML.

Примечание

Компонент Suspense активируется только для источников данных, поддерживающих Suspense. К ним относятся:

  • Получение данных с помощью фреймворков, поддерживающих Suspense, например Relay и Next.js
  • Отложенная загрузка кода компонентов с помощью lazy
  • Чтение значения Promise с помощью use

Suspense не обнаруживает получение данных внутри Effect или обработчика событий.

Точный способ загрузки данных в приведённом выше компоненте 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 prerender(<App />, {
      signal: controller.signal,
    });
    //...

Границы Suspense с незавершёнными дочерними элементами будут включены в прелюдию в резервном состоянии.

Это можно использовать для частичного предварительного рендеринга вместе с resume или resumeAndPrerender.

Устранение неполадок

Поток не начинается, пока не отрендерится всё приложение

Ответ prerender ожидает завершения рендеринга всего приложения, включая разрешение всех границ Suspense. Этот API предназначен для предварительной генерации статических сайтов (SSG) и не поддерживает передачу дополнительного содержимого по мере его загрузки.

Чтобы передавать содержимое по мере его загрузки, используйте потоковый API серверного рендеринга, например renderToReadableStream.

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://react.dev/reference/react-dom/static/prerender

Spec-Zone.ru

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