renderToReadableStream fait le rendu d’un arbre React dans un flux Readable Web Stream.

const stream = await renderToReadableStream(reactNode, options?)

Remarque

Cette API dépend des Web Streams. Pour Node.js, utilisez plutÎt renderToPipeableStream.


Référence

renderToReadableStream(reactNode, options?)

Appelez renderToReadableStream pour faire le rendu HTML d’un arbre React au moyen d’un flux Readable Web Stream.

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

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

CÎté client, appelez hydrateRoot pour rendre interactif ce HTML généré cÎté serveur.

Voir d’autres exemples ci-dessous.

ParamĂštres

  • reactNode : un nƓud React dont vous souhaitez produire le HTML. Ça pourrait par exemple ĂȘtre un Ă©lĂ©ment JSX tel que <App />. C’est censĂ© produire le document entier, de sorte que le composant App devrait produire la balise <html>.

  • options optionnelles : un objet avec des options de streaming.

    • bootstrapScriptContent optionnel : s’il est fourni, ce code source sera placĂ© dans une balise <script> en ligne du flux de sortie.
    • bootstrapScripts optionnels : un tableau d’URL sous format texte pour des balises <script> Ă  Ă©mettre dans la page. Utilisez-le pour inclure le <script> qui appellera hydrateRoot. Vous pouvez vous en passer si vous ne souhaitez pas exĂ©cuter React cĂŽtĂ© client.
    • bootstrapModules optionnels : joue le mĂȘme rĂŽle que bootstrapScripts, mais Ă©met plutĂŽt des balises <script type="module">.
    • identifierPrefix optionnel : un prĂ©fixe textuel utilisĂ© pour les ID gĂ©nĂ©rĂ©s par useId. Pratique pour Ă©viter les conflits entre les ID au sein de racines multiples sur une mĂȘme page. Doit ĂȘtre le mĂȘme prĂ©fixe que celui passĂ© Ă hydrateRoot.
    • namespaceURI optionnel : l’URI textuel de l’espace de noms racine pour le flux. Par dĂ©faut, celui du HTML standard. Passez 'http://www.w3.org/2000/svg' pour SVG ou 'http://www.w3.org/1998/Math/MathML' pour MathML.
    • nonce optionnel : un nonce textuel pour permettre les scripts avec une Content-Security-Policy contenant script-src.
    • onError optionnelle : une fonction de rappel dĂ©clenchĂ©e pour toute erreur serveur, qu’elle soit rĂ©cupĂ©rable ou pas. Par dĂ©faut, ça fait juste un console.error. Si vous l’écrasez pour journaliser les rapports de plantage, assurez-vous de continuer Ă  appeler console.error. Vous pouvez aussi vous en servir pour ajuster le code de rĂ©ponse HTTP avant que l’enveloppe ne soit Ă©mise.
    • progressiveChunkSize optionnel : le nombre d’octets dans un segment d’envoi progressif. Apprenez-en davantage sur l’heuristique par dĂ©faut.
    • signal optionnel : un AbortSignal qui vous permettra d’abandonner le rendu cĂŽtĂ© serveur pour faire le reste du rendu cĂŽtĂ© client.

Valeur renvoyée

renderToReadableStream renvoie une promesse (Promise) :

Le flux obtenu expose une propriĂ©tĂ© complĂ©mentaire :


Utilisation

Faire le rendu d’un arbre React sous forme HTML au moyen d’un flux Readable Web Stream

Appelez renderToReadableStream pour faire le rendu d’un arbre React sous forme HTML dans un flux Readable Web Stream :

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

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

En plus du composant racine, vous devrez fournir une liste des chemins de <script> de démarrage. Votre composant racine doit renvoyer le document intégral, donc la balise <html> racine.

Il pourrait par exemple ressembler Ă  ça :

export default function App() {
return (
<html lang="fr">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>Mon appli</title>
</head>
<body>
<Router />
</body>
</html>
);
}

React injectera le doctype et vos balises <script> de dĂ©marrage dans le flux HTML rĂ©sultant :

<!DOCTYPE html>
<html>
<!-- ... HTML de vos composants ... -->
</html>
<script src="/main.js" async=""></script>

CĂŽtĂ© client, votre script de dĂ©marrage devrait hydrater le document entier avec un appel Ă  hydrateRoot :

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

hydrateRoot(document, <App />);

Ça attachera les gestionnaires d’évĂ©nements au HTML gĂ©nĂ©rĂ© cĂŽtĂ© serveur pour le rendre interactif.

En détail

Lire les chemins de ressources CSS et JS depuis la sortie du build

Les URL finales de ressources (telles que les fichiers JavaScript et CSS) contiennent souvent une empreinte gĂ©nĂ©rĂ©e par le build Par exemple, plutĂŽt que styles.css, vous pourriez vous retrouver avec styles.123456.css. L’injection d’empreinte dans les noms de fichiers des ressources statiques garantit que chaque nouveau build d’une mĂȘme ressource aura un nom diffĂ©rent (si son contenu a changĂ©). C’est pratique pour mettre en place sans danger des stratĂ©gies de cache Ă  long terme pour les ressources statiques : un fichier avec un nom donnĂ© ne changera jamais de contenu.

Seulement voilĂ , si vous ne connaissez pas les URL des ressources avant la fin du build, vous n’avez aucun moyen de les mettre dans votre code source. Par exemple, ça ne servirait Ă  rien de coder en dur "/styles.css" dans votre JSX, comme dans le code vu plus haut. Pour garder les noms finaux hors de votre code source, votre composant racine peut lire les vĂ©ritables noms depuis une table de correspondance qui lui serait passĂ©e en prop :

export default function App({ assetMap }) {
return (
<html>
<head>
...
<link rel="stylesheet" href={assetMap['styles.css']}></link>
...
</head>
...
</html>
);
}

CĂŽtĂ© serveur, faites le rendu de <App assetMap={assetMap} /> et passez-lui une assetMap avec les URL des ressources :

// Vous devrez récupérer ce JSON depuis votre outil de build,
// par exemple en lisant son affichage résultat.
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);
}
});
});

Dans la mesure oĂč votre serveur fait dĂ©sormais le rendu de <App assetMap={assetMap} />, vous devez lui passer cette assetMap cĂŽtĂ© client aussi, pour Ă©viter toute erreur lors de l’hydratation. Vous pouvez la sĂ©rialiser et la passer au client comme ceci :

// Vous récupérereriez ce JSON depuis votre outil de build.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};

app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream(<App assetMap={assetMap} />, {
// Attention : on peut stringify() ça sans danger parce que ça ne vient pas des utilisateurs.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['main.js']],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});

Dans l’exemple ci-dessus, l’option bootstrapScriptContent ajoute une balise <script> en ligne complĂ©mentaire qui dĂ©finit la variable globale window.assetMap cĂŽtĂ© client. Ça permet au code client de lire la mĂȘme assetMap :

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

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

À prĂ©sent le client comme le serveur font le rendu d’App avec la mĂȘme prop assetMap, on n’a donc pas d’erreur d’hydratation.


Streamer plus de contenu au fil du chargement

Le streaming permet Ă  l’utilisateur de commencer Ă  voir votre contenu avant mĂȘme que toutes les donnĂ©es soient chargĂ©es cĂŽtĂ© serveur. Imaginez par exemple une page de profil qui affiche une image de couverture, une barre latĂ©rale avec des ami·e·s et leurs photos, et une liste d’articles :

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

Imaginez que le chargement des donnĂ©es de <Posts /> prenne du temps. Dans l’idĂ©al, vous aimeriez afficher le reste du contenu de la page profil Ă  l’utilisateur, sans le forcer Ă  attendre d’abord les articles. Pour y parvenir, enrobez Posts dans un pĂ©rimĂštre <Suspense> :

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

Ça demande Ă  React de commencer Ă  streamer le HTML avant que Posts ne charge ses donnĂ©es. React enverra dans un premier temps le HTML du contenu de secours (PostsGlimmer) puis, quand Posts aura fini de charger ses donnĂ©es, React enverra le HTML restant ainsi qu’une balise <script> intĂ©grĂ©e qui remplacera le contenu de secours avec ce HTML. Du point de vue de l’utilisateur, la page apparaĂźtra d’abord avec le PostsGlimmer, qui sera ensuite remplacĂ© par les Posts.

Vous pouvez mĂȘme imbriquer les pĂ©rimĂštres <Suspense> afin de crĂ©er des sĂ©quences de chargement avec une granularitĂ© plus fine :

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

Dans cet exemple, React commencera Ă  streamer la page encore plus tĂŽt. Seuls ProfileLayout et ProfileCover devront d’abord terminer leur rendu, car ils ne sont enrobĂ©s dans aucun pĂ©rimĂštre <Suspense>. En revanche, si Sidebar, Friends ou Photos ont besoin de charger des donnĂ©es, React enverra le HTML du contenu de secours BigSpinner Ă  leur place. Ainsi, au fil de la mise Ă  disposition des donnĂ©es, davantage de contenu continuera Ă  ĂȘtre affichĂ© jusqu’à ce que tout soit enfin visible.

Le streaming n’a pas besoin d’attendre que React lui-mĂȘme soit chargĂ© dans le navigateur, ou que votre appli soit devenue interactive. Le contenu HTML gĂ©nĂ©rĂ© cĂŽtĂ© serveur sera envoyĂ© et affichĂ© progressivement avant le chargement de n’importe quelle balise <script>.

Apprenez-en davantage sur le fonctionnement du streaming HTML.

Remarque

Seules les sources de donnĂ©es compatibles Suspense activeront le composant Suspense. Elles comprennent :

  • Le chargement de donnĂ©es fourni par des frameworks intĂ©grant Suspense tels que Relay et Next.js
  • Le chargement Ă  la demande du code de composants avec lazy
  • La lecture de la valeur d’une promesse avec use

Suspense ne dĂ©tecte pas le chargement de donnĂ©es depuis un Effet ou un gestionnaire d’évĂ©nement.

Les modalités exactes de votre chargement de données dans le composant Posts ci-dessus dépenderont de votre framework. Si vous utilisez un framework intégrant Suspense, vous trouverez tous les détails dans sa documentation sur le chargement de données.

Le chargement de donnĂ©es compatible avec Suspense sans recourir Ă  un framework spĂ©cifique n’est pas encore pris en charge. Les spĂ©cifications d’implĂ©mentation d’une source de donnĂ©es intĂ©grant Suspense sont encore instables et non documentĂ©es. Une API officielle pour intĂ©grer les sources de donnĂ©es avec Suspense sera publiĂ©e dans une future version de React.


SpĂ©cifier le contenu de l’enveloppe

La partie de votre appli Ă  l’extĂ©rieur de tout pĂ©rimĂštre <Suspense> est appelĂ©e l’enveloppe :

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

Elle détermine le tout premier état de chargement que vos utilisateurs sont susceptibles de voir :

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

Si vous enrobez toute l’appli dans un pĂ©rimĂštre <Suspense> Ă  la racine, l’enveloppe ne contiendra qu’un indicateur de chargement. Ce n’est hĂ©las pas une expĂ©rience utilisateur agrĂ©able, car voir un gros indicateur de chargement Ă  l’écran peut sembler plus lent et plus irritant que d’attendre un instant pour voir arriver la vĂ©ritable mise en page. C’est pourquoi vous voudrez gĂ©nĂ©ralement positionner vos pĂ©rimĂštres <Suspense> de façon Ă  ce que l’enveloppe donne une impression minimale mais complĂšte — comme si elle reprĂ©sentait un squelette intĂ©gral de la page.

Un appel asynchrone Ă  renderToReadableStream s’accomplira avec un stream lorsque l’enveloppe entiĂšre a fini son rendu. C’est gĂ©nĂ©ralement lĂ  que vous commencerez le streaming en crĂ©ant puis renvoyant une rĂ©ponse basĂ©e sur le stream :

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

Lorsque la rĂ©ponse basĂ©e sur le stream est renvoyĂ©e, les composants Ă  l’intĂ©rieur des pĂ©rimĂštres <Suspense> peuvent encore ĂȘtre en train de charger leurs donnĂ©es.


Journaliser les plantages cÎté serveur

Par dĂ©faut, toutes les erreurs cĂŽtĂ© serveur sont affichĂ©es dans la console. Vous pouvez remplacer ce comportement pour journaliser vos rapports de plantage :

async function handler(request) {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}

Si vous fournissez votre propre implĂ©mentation pour onError, n’oubliez pas de continuer Ă  afficher les erreurs en console, comme ci-dessus.


Se rĂ©tablir aprĂšs une erreur dans l’enveloppe

Dans l’exemple ci-dessous, l’enveloppe contient ProfileLayout, ProfileCover et PostsGlimmer :

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

Si une erreur survient lors du rendu de ces composants, React n’aura pas de HTML exploitable Ă  envoyer au client. Enrobez votre appel Ă  renderToReadableStream dans un try...catch pour envoyer en dernier recours un HTML de secours qui n’aurait pas besoin d’un rendu cĂŽtĂ© serveur :

async function handler(request) {
try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Ça sent le pĂąté </h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

Si une erreur est survenue lors de la gĂ©nĂ©ration de l’enveloppe, tant onError que votre bloc catch seront dĂ©clenchĂ©s. Utilisez onError pour signaler l’erreur et votre bloc catch pour envoyer le document HTML de secours. Votre HTML de secours n’est d’ailleurs pas nĂ©cessairement une page d’erreur. Vous pourriez plutĂŽt proposer une enveloppe alternative qui affiche votre appli en mode 100% client.


Se rĂ©tablir aprĂšs une erreur hors de l’enveloppe

Dans l’exemple qui suit, le composant <Posts /> est enrobĂ© par <Suspense>, ce qui signifie qu’il ne fait pas partie de l’enveloppe :

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

Si une erreur survient au sein du composant Posts ou d’un de ses enfants, React tentera automatiquement de retomber sur ses pieds :

  1. Il émettra le contenu de secours du périmÚtre <Suspense> le plus proche (PostsGlimmer) dans le HTML.
  2. Il « laissera tomber Â» le rendu cĂŽtĂ© serveur du contenu de Posts.
  3. Lorsque le code JavaScript cÎté client aura fini de charger, React retentera le rendu de Posts, cÎté client.

Si la tentative de rendu de Posts cĂŽtĂ© client plante aussi, React lĂšvera l’erreur cĂŽtĂ© client. Comme pour toutes les erreurs survenant lors du rendu, le pĂ©rimĂštre d’erreur le plus proche dĂ©termine la façon dont l’erreur sera prĂ©sentĂ©e Ă  l’utilisateur. En pratique, ça signifie que l’utilisateur verra un indicateur de chargement jusqu’à ce que React soit certain que l’erreur n’est pas rĂ©cupĂ©rable.

Si la tentative de rendu de Posts cĂŽtĂ© client rĂ©ussit, l’indicateur de chargement issu du serveur sera remplacĂ© par le rĂ©sultat du rendu cĂŽtĂ© client. L’utilisateur ne saura pas qu’une erreur est survenue cĂŽtĂ© serveur. En revanche, les fonctions de rappel onError cĂŽtĂ© serveur et onRecoverableError cĂŽtĂ© client seront dĂ©clenchĂ©es pour que vous soyez notifié·e de l’erreur.


Définir le code de réponse HTTP

Le streaming implique des compromis. Vous souhaitez commencer Ă  streamer la page aussitĂŽt que possible, pour que l’utilisateur voie du contenu plus tĂŽt. Seulement, dĂšs que vous commencer Ă  streamer, vous ne pouvez plus dĂ©finir le code de rĂ©ponse HTTP.

En dĂ©coupant votre appli avec d’un cĂŽtĂ© l’enveloppe (au-dessus de tous les pĂ©rimĂštres <Suspense>) et de l’autre le reste du contenu, vous avez dĂ©jĂ  en partie rĂ©solu ce problĂšme. Si l’enveloppe rencontre une erreur, ça exĂ©cutera votre bloc catch qui vous permettra de dĂ©finir le code de rĂ©ponse pour l’erreur. Dans le cas contraire, vous savez que l’appli devrait ĂȘtre capable de se rĂ©tablir cĂŽtĂ© client, vous pouvez donc envoyer « OK Â».

async function handler(request) {
try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Ça sent le pĂąté </h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

Si un composant hors de l’enveloppe (par exemple dans un pĂ©rimĂštre <Suspense>) lĂšve une erreur, React n’arrĂȘtera pas le rendu. Ça signifie que la fonction de rappel onError sera dĂ©clenchĂ©e, mais votre code continuera Ă  s’exĂ©cuter sans entrer dans le bloc catch. C’est parce que React tentera de retomber sur ses pieds cĂŽtĂ© client, comme dĂ©crit plus haut.

Ceci Ă©tant dit, si vous prĂ©fĂ©rez, vous pouvez prendre en compte la survenue d’une erreur pour ajuster votre code de rĂ©ponse HTTP :

async function handler(request) {
try {
let didError = false;
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Ça sent le pĂąté </h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

Ça ne capturera que les erreurs hors de l’enveloppe qui sont survenues pendant le rendu initial de l’enveloppe, ce n’est donc pas exhaustif. Si vous estimez impĂ©ratif de savoir si une erreur est survenue pour un contenu donnĂ©, vous pouvez le dĂ©placer dans l’enveloppe.


DiffĂ©rencier la gestion selon l’erreur rencontrĂ©e

Vous pouvez crĂ©er vos propres sous-classes d’Error et utiliser l’opĂ©rateur instanceof pour dĂ©terminer quelle erreur est survenue. Vous pouvez par exemple dĂ©finir une NotFoundError sur-mesure et la lever depuis votre composant. À partir de lĂ , vous pouvez sauvegarder l’erreur dans onError pour diffĂ©rencier ensuite votre gestion en fonction du type d’erreur :

async function handler(request) {
let didError = false;
let caughtError = null;

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

try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Ça sent le pĂąté </h1>', {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
}
}

Gardez Ă  l’esprit qu’une fois que vous avez Ă©mis l’enveloppe et commencĂ© Ă  streamer, vous ne pourrez plus changer le code de rĂ©ponse HTTP.


Attendre que tout le contenu soit chargĂ© pour les moteurs d’indexation web et la gĂ©nĂ©ration statique

Le streaming offre une meilleure expĂ©rience utilisateur parce que l’utilisateur peut voir le contenu au fur et Ă  mesure de sa mise Ă  disposition.

Ceci Ă©tant, lorsqu’un moteur d’indexation web visite votre page, ou si vous gĂ©nĂ©rez ces pages au moment du build, vous pourriez vouloir attendre que tout le contenu soit d’abord chargĂ© pour ensuite produire le rĂ©sultat HTML final, plutĂŽt que de le rĂ©vĂ©ler progressivement.

Vous pouvez attendre que tout le contenu soit disponible en attendant la promesse stream.allReady :

async function handler(request) {
try {
let didError = false;
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
let isCrawler = // ... dépend de votre stratégie de détection de bot ...
if (isCrawler) {
await stream.allReady;
}
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Ça sent le pĂąté </h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

Un visiteur normal recevra le flux de contenu chargĂ© progressivement. Un moteur d’indexation web recevra le rĂ©sultat HTML final, une fois toutes les donnĂ©es chargĂ©es. Ça signifie cependant que ce moteur devra attendre toutes les donnĂ©es, dont certaines peuvent ĂȘtre lentes Ă  charger ou causer une erreur. Selon la nature de votre appli, vous pourriez choisir d’envoyer juste l’enveloppe aux moteurs d’indexation web.


Abandonner le rendu cÎté serveur

Vous pouvez forcer le rendu cĂŽtĂ© serveur Ă  « laisser tomber Â» au bout d’un certain temps :

async function handler(request) {
try {
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, 10000);

const stream = await renderToReadableStream(<App />, {
signal: controller.signal,
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
// ...

React enverra les contenus de secours restants en HTML puis tentera de faire la fin du rendu cÎté client.