renderToReadableStream
renderToReadableStream
fait le rendu dâun arbre React dans un flux Readable Web Stream.
const stream = await renderToReadableStream(reactNode, options?)
- Référence
- Utilisation
- Faire le rendu dâun arbre React sous forme HTML au moyen dâun flux Readable Web Stream
- Streamer plus de contenu au fil du chargement
- SpĂ©cifier le contenu de lâenveloppe
- Journaliser les plantages cÎté serveur
- Se rĂ©tablir aprĂšs une erreur dans lâenveloppe
- Se rĂ©tablir aprĂšs une erreur hors de lâenveloppe
- Définir le code de réponse HTTP
- DiffĂ©rencier la gestion selon lâerreur rencontrĂ©e
- Attendre que tout le contenu soit chargĂ© pour les moteurs dâindexation web et la gĂ©nĂ©ration statique
- Abandonner le rendu cÎté serveur
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 composantApp
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 appellerahydrateRoot
. Vous pouvez vous en passer si vous ne souhaitez pas exécuter React cÎté client.bootstrapModules
optionnels : joue le mĂȘme rĂŽle quebootstrapScripts
, mais émet plutÎt des balises<script type="module">
.identifierPrefix
optionnel : un préfixe textuel utilisé pour les ID générés paruseId
. 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 : unnonce
textuel pour permettre les scripts avec une Content-Security-Policy contenantscript-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 unconsole.error
. Si vous lâĂ©crasez pour journaliser les rapports de plantage, assurez-vous de continuer Ă appelerconsole.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 : unAbortSignal
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
) :
- Si le rendu de lâenveloppe rĂ©ussit, cette promesse sâaccomplira avec un flux Readable Web Stream.
- Si le rendu de lâenveloppe Ă©choue, cette promesse sera rejetĂ©e. Utilisez ce scĂ©nario pour produire une enveloppe de secours.
Le flux obtenu expose une propriété complémentaire :
allReady
: une promesse qui sâaccomplira lorsque le rendu sera complĂštement terminĂ©, y compris lâenveloppe et tout le contenu additionnel. Vous pouvez faire unawait stream.allReady
avant de renvoyer une rĂ©ponse pour les moteurs dâindexation web et la gĂ©nĂ©ration statique. Si vous optez pour cette approche, vous nâaurez pas de chargement progressif. Le flux contiendra le HTML final.
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
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.
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 :
- Il émettra le contenu de secours du périmÚtre
<Suspense>
le plus proche (PostsGlimmer
) dans le HTML. - Il « laissera tomber » le rendu cÎté serveur du contenu de
Posts
. - 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.