À mesure que votre candidature grandit, les défis évoluent également. Pour garder une longueur d'avance, la maîtrise des techniques SSR avancées est essentielle pour offrir une expérience utilisateur fluide et performante.
Après avoir jeté les bases du rendu côté serveur dans les projets React dans l'article précédent, je suis ravi de partager des fonctionnalités qui peuvent vous aider à maintenir l'évolutivité du projet, à charger efficacement les données du serveur vers le client et à résoudre les problèmes d'hydratation.
Table des matières
- Qu'est-ce que le streaming en SSR
- Chargement paresseux et SSR
-
Implémentation du streaming avec chargement différé
- Mise à jour des composants React
- Mise à jour du serveur pour le streaming
-
Données serveur à client
- Transmission de données sur le serveur
- Gérer les variables d'environnement sur le client
-
Problèmes d'hydratation
- Exemple de scénario
- Résoudre les problèmes d'hydratation
- Conclusion
Qu'est-ce que le streaming en RSS
Le streaming dans le rendu côté serveur (SSR) est une technique dans laquelle le serveur envoie des parties de la page HTML au navigateur par morceaux au fur et à mesure de leur génération, plutôt que d'attendre que la page entière soit prête. avant de le livrer. Cela permet au navigateur de commencer à restituer le contenu immédiatement, améliorant ainsi les temps de chargement et les performances de l'utilisateur.
Le streaming est particulièrement efficace pour :
- Grandes pages : où la génération de l'intégralité du code HTML peut prendre beaucoup de temps.
- Contenu dynamique : lorsque des parties de la page dépendent d'appels d'API externes ou de morceaux générés dynamiquement.
- Applications à fort trafic : pour réduire la charge et la latence du serveur pendant les pics d'utilisation.
Le streaming comble le fossé entre le SSR traditionnel et l'interactivité moderne côté client, garantissant aux utilisateurs de voir un contenu significatif plus rapidement sans compromettre les performances.
Chargement paresseux et SSR
LeLe chargement paresseux est une technique qui diffère le chargement de composants ou de modules jusqu'à ce qu'ils soient réellement nécessaires, réduisant ainsi le temps de chargement initial et améliorant les performances. Lorsqu'il est combiné avec SSR, le chargement paresseux peut optimiser considérablement les charges de travail du serveur et du client.
Le chargement paresseux repose sur React.lazy, qui importe dynamiquement des composants sous forme de promesses. Dans le SSR traditionnel, le le rendu est synchrone, ce qui signifie que le serveur doit résoudre toutes les promesses avant de générer et d'envoyer le code HTML complet au navigateur.
Le streaming résout ces problèmes en permettant au serveur d'envoyer du HTML par morceaux au fur et à mesure du rendu des composants. Cette approche permet d'envoyer immédiatement la solution de secours Suspense au navigateur, garantissant ainsi aux utilisateurs de voir rapidement un contenu significatif. Au fur et à mesure que les composants chargés paresseux sont résolus, leur HTML rendu est diffusé progressivement vers le navigateur, remplaçant de manière transparente le contenu de secours. Cela évite de bloquer le processus de rendu, réduit les retards et améliore les temps de chargement perçus.
Implémentation du streaming avec chargement différé
Ce guide s'appuie sur les concepts introduits dans l'article précédent, Création d'applications SSR React prêtes pour la production, dont vous pouvez trouver le lien en bas. Pour activer SSR avec React et prendre en charge les composants à chargement différé, nous effectuerons plusieurs mises à jour des composants React et du serveur.
Mise à jour des composants React
Point d'entrée du serveur
La méthode renderToString de React est couramment utilisée pour SSR, mais elle attend que l'intégralité du contenu HTML soit prête avant de l'envoyer au navigateur. En passant à renderToPipeableStream, nous pouvons activer le streaming, qui envoie des parties du HTML au fur et à mesure de leur génération.
// ./src/entry-server.tsx import { renderToPipeableStream, RenderToPipeableStreamOptions } from 'react-dom/server' import App from './App' export function render(options?: RenderToPipeableStreamOptions) { return renderToPipeableStream(<app></app>, options) }
Création d'un composant à chargement différé
Dans cet exemple, nous allons créer un composant Card simple pour démontrer le concept. Dans les applications de production, cette technique est généralement utilisée avec des modules plus grands ou des pages entières pour optimiser les performances.
// ./src/Card.tsx import { useState } from 'react' function Card() { const [count, setCount] = useState(0) return ( <div classname="card"> <button onclick="{()"> setCount((count) => count + 1)}> count is {count} </button> <p> Edit <code>src/App.tsx</code> and save to test HMR </p> </div> ) } export default Card
Utilisation du composant Lazy-Loaded dans l'application
Pour utiliser le composant chargé paresseux, importez-le dynamiquement à l'aide de React.lazy et enveloppez-le avec Suspense pour fournir une interface utilisateur de secours pendant le chargement
// ./src/App.tsx import { lazy, Suspense } from 'react' import reactLogo from './assets/react.svg' import viteLogo from '/vite.svg' import './App.css' const Card = lazy(() => import('./Card')) function App() { return ( <div> <a href="https://vite.dev" target="_blank"> <img src="%7BviteLogo%7D" classname="logo" alt="Vite logo"> </a> <a href="https://react.dev" target="_blank"> <img src="%7BreactLogo%7D" classname="logo react" alt="React logo"> </a> </div> <h1 id="Vite-React">Vite + React</h1> <suspense fallback="Loading..."> <card></card> </suspense> <p classname="read-the-docs"> Click on the Vite and React logos to learn more </p> > ) } export default App
Mise à jour du serveur pour le streaming
Pour permettre le streaming, les configurations de développement et de production doivent prendre en charge un processus de rendu HTML cohérent. Étant donné que le processus est le même pour les deux environnements, vous pouvez créer une seule fonction réutilisable pour gérer efficacement le contenu en streaming.
Création d'une fonction de contenu de flux
// ./server/constants.ts export const ABORT_DELAY = 5000
La fonction streamContent lance le processus de rendu, écrit des morceaux incrémentiels de code HTML dans la réponse et garantit une gestion appropriée des erreurs.
// ./server/streamContent.ts import { Transform } from 'node:stream' import { Request, Response, NextFunction } from 'express' import { ABORT_DELAY, HTML_KEY } from './constants' import type { render } from '../src/entry-server' export type StreamContentArgs = { render: typeof render html: string req: Request res: Response next: NextFunction } export function streamContent({ render, html, res }: StreamContentArgs) { let renderFailed = false // Initiates the streaming process by calling the render function const { pipe, abort } = render({ // Handles errors that occur before the shell is ready onShellError() { res.status(500).set({ 'Content-Type': 'text/html' }).send('<pre class="brush:php;toolbar:false">Something went wrong') }, // Called when the shell (initial HTML) is ready for streaming onShellReady() { res.status(renderFailed ? 500 : 200).set({ 'Content-Type': 'text/html' }) // Split the HTML into two parts using the placeholder const [htmlStart, htmlEnd] = html.split(HTML_KEY) // Write the starting part of the HTML to the response res.write(htmlStart) // Create a transform stream to handle the chunks of HTML from the renderer const transformStream = new Transform({ transform(chunk, encoding, callback) { // Write each chunk to the response res.write(chunk, encoding) callback() }, }) // When the streaming is finished, write the closing part of the HTML transformStream.on('finish', () => { res.end(htmlEnd) }) // Pipe the render output through the transform stream pipe(transformStream) }, onError(error) { // Logs errors encountered during rendering renderFailed = true console.error((error as Error).stack) }, }) // Abort the rendering process after a delay to avoid hanging requests setTimeout(abort, ABORT_DELAY) }
Mise à jour de la configuration de développement
// ./server/dev.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import { StreamContentArgs } from './streamContent' const HTML_PATH = path.resolve(process.cwd(), 'index.html') const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx') // Add to args the streamContent callback export async function setupDev(app: Application, streamContent: (args: StreamContentArgs) => void) { const vite = await ( await import('vite') ).createServer({ root: process.cwd(), server: { middlewareMode: true }, appType: 'custom', }) app.use(vite.middlewares) app.get('*', async (req, res, next) => { try { let html = fs.readFileSync(HTML_PATH, 'utf-8') html = await vite.transformIndexHtml(req.originalUrl, html) const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH) // Use the same callback for production and development process streamContent({ render, html, req, res, next }) } catch (e) { vite.ssrFixStacktrace(e as Error) console.error((e as Error).stack) next(e) } }) }
Mise à jour de la configuration de production
// ./server/prod.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import compression from 'compression' import sirv from 'sirv' import { StreamContentArgs } from './streamContent' const CLIENT_PATH = path.resolve(process.cwd(), 'dist/client') const HTML_PATH = path.resolve(process.cwd(), 'dist/client/index.html') const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'dist/ssr/entry-server.js') // Add to Args the streamContent callback export async function setupProd(app: Application, streamContent: (args: StreamContentArgs) => void) { app.use(compression()) app.use(sirv(CLIENT_PATH, { extensions: [] })) app.get('*', async (req, res, next) => { try { const html = fs.readFileSync(HTML_PATH, 'utf-8') const { render } = await import(ENTRY_SERVER_PATH) // Use the same callback for production and development process streamContent({ render, html, req, res, next }) } catch (e) { console.error((e as Error).stack) next(e) } }) }
Mise à jour du serveur Express
Passez la fonction streamContent à chaque configuration :
// ./server/app.ts import express from 'express' import { PROD, APP_PORT } from './constants' import { setupProd } from './prod' import { setupDev } from './dev' import { streamContent } from './streamContent' export async function createServer() { const app = express() if (PROD) { await setupProd(app, streamContent) } else { await setupDev(app, streamContent) } app.listen(APP_PORT, () => { console.log(`http://localhost:${APP_PORT}`) }) } createServer()
Après avoir mis en œuvre ces modifications, votre serveur :
- Diffusez progressivement du HTML vers le navigateur, réduisant ainsi le temps nécessaire à la première peinture.
- Gérez de manière transparente les composants chargés paresseux, améliorant à la fois les performances et l'expérience utilisateur.
Données serveur à client
Avant d'envoyer du HTML au client, vous avez un contrôle total sur le HTML généré par le serveur. Cela vous permet de modifier dynamiquement la structure en ajoutant des balises, des styles, des liens ou tout autre élément selon vos besoins.
Une technique particulièrement puissante consiste à injecter un <script> balise dans le HTML. Cette approche vous permet de transmettre des données dynamiques directement au client.</script>
Dans cet exemple, nous nous concentrerons sur la transmission d'une variable d'environnement, mais vous pouvez transmettre n'importe quel objet JavaScript dont vous avez besoin. En transmettant des variables d'environnement au client, vous évitez de reconstruire l'intégralité de l'application lorsque ces variables changent. Dans l'exemple de référentiel lié en bas, vous pouvez également voir comment les données de profil sont transmises dynamiquement.
Transmission de données sur le serveur
Définir API_URL
Définissez une variable d'environnement API_URL sur le serveur. Par défaut, cela pointera vers jsonplaceholder. Le __INITIAL_DATA__ fera office de clé pour stocker les données sur l'objet fenêtre global.
// ./src/entry-server.tsx import { renderToPipeableStream, RenderToPipeableStreamOptions } from 'react-dom/server' import App from './App' export function render(options?: RenderToPipeableStreamOptions) { return renderToPipeableStream(<app></app>, options) }
Injecter les données initiales dans HTML
Créez une fonction utilitaire pour injecter les données initiales dans la chaîne HTML avant de l'envoyer au client. Ces données incluront des variables d'environnement comme API_URL.
// ./src/Card.tsx import { useState } from 'react' function Card() { const [count, setCount] = useState(0) return ( <div classname="card"> <button onclick="{()"> setCount((count) => count + 1)}> count is {count} </button> <p> Edit <code>src/App.tsx</code> and save to test HMR </p> </div> ) } export default Card
Mettre à jour le contenu du flux
Utilisez la fonction applyInitialData pour injecter les données initiales dans le HTML et l'envoyer au client.
// ./src/App.tsx import { lazy, Suspense } from 'react' import reactLogo from './assets/react.svg' import viteLogo from '/vite.svg' import './App.css' const Card = lazy(() => import('./Card')) function App() { return ( <div> <a href="https://vite.dev" target="_blank"> <img src="%7BviteLogo%7D" classname="logo" alt="Vite logo"> </a> <a href="https://react.dev" target="_blank"> <img src="%7BreactLogo%7D" classname="logo react" alt="React logo"> </a> </div> <h1 id="Vite-React">Vite + React</h1> <suspense fallback="Loading..."> <card></card> </suspense> <p classname="read-the-docs"> Click on the Vite and React logos to learn more </p> > ) } export default App
Gérer les variables d'environnement sur le client
Étendre la fenêtre Globale
Mettez à jour la déclaration de type globale pour inclure la clé __INITIAL_DATA__ et sa structure.
// ./server/constants.ts export const ABORT_DELAY = 5000
Accédez à API_URL à partir de l'objet Window
// ./server/streamContent.ts import { Transform } from 'node:stream' import { Request, Response, NextFunction } from 'express' import { ABORT_DELAY, HTML_KEY } from './constants' import type { render } from '../src/entry-server' export type StreamContentArgs = { render: typeof render html: string req: Request res: Response next: NextFunction } export function streamContent({ render, html, res }: StreamContentArgs) { let renderFailed = false // Initiates the streaming process by calling the render function const { pipe, abort } = render({ // Handles errors that occur before the shell is ready onShellError() { res.status(500).set({ 'Content-Type': 'text/html' }).send('<pre class="brush:php;toolbar:false">Something went wrong') }, // Called when the shell (initial HTML) is ready for streaming onShellReady() { res.status(renderFailed ? 500 : 200).set({ 'Content-Type': 'text/html' }) // Split the HTML into two parts using the placeholder const [htmlStart, htmlEnd] = html.split(HTML_KEY) // Write the starting part of the HTML to the response res.write(htmlStart) // Create a transform stream to handle the chunks of HTML from the renderer const transformStream = new Transform({ transform(chunk, encoding, callback) { // Write each chunk to the response res.write(chunk, encoding) callback() }, }) // When the streaming is finished, write the closing part of the HTML transformStream.on('finish', () => { res.end(htmlEnd) }) // Pipe the render output through the transform stream pipe(transformStream) }, onError(error) { // Logs errors encountered during rendering renderFailed = true console.error((error as Error).stack) }, }) // Abort the rendering process after a delay to avoid hanging requests setTimeout(abort, ABORT_DELAY) }
Faire une demande à l'aide de l'API_URL dynamique
// ./server/dev.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import { StreamContentArgs } from './streamContent' const HTML_PATH = path.resolve(process.cwd(), 'index.html') const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx') // Add to args the streamContent callback export async function setupDev(app: Application, streamContent: (args: StreamContentArgs) => void) { const vite = await ( await import('vite') ).createServer({ root: process.cwd(), server: { middlewareMode: true }, appType: 'custom', }) app.use(vite.middlewares) app.get('*', async (req, res, next) => { try { let html = fs.readFileSync(HTML_PATH, 'utf-8') html = await vite.transformIndexHtml(req.originalUrl, html) const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH) // Use the same callback for production and development process streamContent({ render, html, req, res, next }) } catch (e) { vite.ssrFixStacktrace(e as Error) console.error((e as Error).stack) next(e) } }) }
Vous disposez désormais d'une variable d'environnement dynamique disponible dans votre code côté client, vous permettant de gérer les données serveur-client sans avoir besoin de reconstruire le bundle JavaScript. Cette approche simplifie la configuration et rend votre application plus flexible et évolutive.
Problèmes d'hydratation
Maintenant que vous pouvez transmettre des données du serveur au client, vous pourriez rencontrer des problèmes d'hydratation si vous essayez d'utiliser ces données directement dans un composant. Ces erreurs se produisent parce que le HTML rendu par le serveur ne correspond pas au rendu React initial sur le client.
Exemple de scénario
Envisagez d'utiliser API_URL comme simple chaîne dans votre composant
// ./src/entry-server.tsx import { renderToPipeableStream, RenderToPipeableStreamOptions } from 'react-dom/server' import App from './App' export function render(options?: RenderToPipeableStreamOptions) { return renderToPipeableStream(<app></app>, options) }
Dans ce cas, le serveur restituera le composant avec API_URL sous forme de chaîne vide, mais sur le client, API_URL aura déjà une valeur provenant de l'objet fenêtre. Cette incompatibilité provoque une erreur d'hydratation car React détecte une différence entre le code HTML rendu par le serveur et l'arborescence React du client.
Bien que l'utilisateur puisse voir le contenu se mettre à jour rapidement, React enregistre un avertissement d'hydratation dans la console. Pour résoudre ce problème, vous devez vous assurer que le serveur et le client affichent le même HTML initial ou transmettent explicitement API_URL au point d'entrée du serveur.
Résoudre les problèmes d’hydratation
Pour résoudre l'erreur, transmettez initialData au composant App via le point d'entrée du serveur.
Mettre à jour le contenu du flux
// ./src/Card.tsx import { useState } from 'react' function Card() { const [count, setCount] = useState(0) return ( <div classname="card"> <button onclick="{()"> setCount((count) => count + 1)}> count is {count} </button> <p> Edit <code>src/App.tsx</code> and save to test HMR </p> </div> ) } export default Card
Gérer les données dans la fonction de rendu
// ./src/App.tsx import { lazy, Suspense } from 'react' import reactLogo from './assets/react.svg' import viteLogo from '/vite.svg' import './App.css' const Card = lazy(() => import('./Card')) function App() { return ( <div> <a href="https://vite.dev" target="_blank"> <img src="%7BviteLogo%7D" classname="logo" alt="Vite logo"> </a> <a href="https://react.dev" target="_blank"> <img src="%7BreactLogo%7D" classname="logo react" alt="React logo"> </a> </div> <h1 id="Vite-React">Vite + React</h1> <suspense fallback="Loading..."> <card></card> </suspense> <p classname="read-the-docs"> Click on the Vite and React logos to learn more </p> > ) } export default App
Utiliser initialData dans le composant d'application
// ./server/constants.ts export const ABORT_DELAY = 5000
Maintenant, le HTML rendu par votre serveur correspondra au rendu React initial sur le client, éliminant ainsi les erreurs d'hydratation. React réconciliera correctement les arborescences serveur et client, garantissant une expérience transparente.
Pour les données dynamiques comme API_URL, pensez à utiliser React Context pour gérer et transmettre les valeurs par défaut entre le serveur et le client. Cette approche simplifie la gestion des données partagées entre les composants. Vous pouvez trouver un exemple d'implémentation dans le référentiel lié en bas.
Conclusion
Dans cet article, nous avons exploré les techniques SSR avancées pour React, en nous concentrant sur la mise en œuvre du streaming, la gestion des données serveur-client et la résolution des problèmes d'hydratation. Ces méthodes garantissent que votre application est évolutive, performante et créent des expériences utilisateur transparentes.
Explorez le code
- Exemple : react-ssr-advanced-example
- Modèle : react-ssr-streaming-template
- Modèle supplémentaire Vite : template-ssr-react-streaming-ts
Articles connexes
Cela fait partie de ma série sur la RSS avec React. Restez à l'écoute pour plus d'articles !
- Créer des applications SSR React prêtes pour la production
- Techniques React SSR avancées avec streaming et données dynamiques (Vous êtes ici)
- Configuration de thèmes dans les applications SSR React (à venir)
Restez connecté
Je suis toujours ouvert aux commentaires, à la collaboration ou aux discussions sur des idées techniques – n'hésitez pas à nous contacter !
- Portfolio : maxh1t.xyz
- E-mail : m4xh17@gmail.com
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Explication détaillée de la méthode de remplacement de la chaîne JavaScript et de la FAQ Cet article explorera deux façons de remplacer les caractères de chaîne dans JavaScript: le code JavaScript interne et le HTML interne pour les pages Web. Remplacer la chaîne dans le code JavaScript Le moyen le plus direct consiste à utiliser la méthode Remplace (): str = str.replace ("trouver", "remplacer"); Cette méthode remplace uniquement la première correspondance. Pour remplacer toutes les correspondances, utilisez une expression régulière et ajoutez le drapeau global G: str = str.replace (/ fi

Des fonctions JavaScript simples sont utilisées pour vérifier si une date est valide. fonction isValidDate (s) { var bits = s.split ('/'); var d = new Date (bits [2] '/' bits [1] '/' bits [0]); return !! (d && (d.getMonth () 1) == bits [1] && d.getDate () == Number (bits [0])); } //test var

Cet article explique comment utiliser jQuery pour obtenir et définir les valeurs de marge et de marge intérieures des éléments DOM, en particulier les emplacements spécifiques de la marge extérieure et des marges intérieures de l'élément. Bien qu'il soit possible de définir les marges intérieures et extérieures d'un élément à l'aide de CSS, l'obtention de valeurs précises peut être délicate. // installation $ ("div.header"). CSS ("marge", "10px"); $ ("div.header"). css ("padding", "10px"); Vous pourriez penser que ce code est

Cet article explore dix onglets jQuery exceptionnels et accordéons. La principale différence entre les onglets et les accordéons réside dans la façon dont leurs panneaux de contenu sont affichés et cachés. Plongeons ces dix exemples. Articles connexes: 10 plugins de l'onglet jQuery

Découvrez dix plugins jQuery exceptionnels pour élever le dynamisme et l'attrait visuel de votre site Web! Cette collection organisée offre diverses fonctionnalités, de l'animation d'image aux galeries interactives. Explorons ces outils puissants: Related Posts: 1

HTTP-Console est un module de nœud qui vous donne une interface de ligne de commande pour exécuter les commandes HTTP. C'est idéal pour le débogage et voir exactement ce qui se passe avec vos demandes HTTP, qu'elles soient faites contre un serveur Web, Web Serv

Ce tutoriel vous montre comment intégrer une API de recherche Google personnalisée dans votre blog ou site Web, offrant une expérience de recherche plus raffinée que les fonctions de recherche de thème WordPress standard. C'est étonnamment facile! Vous pourrez restreindre les recherches à Y

L'extrait de code jQuery suivant peut être utilisé pour ajouter des barres de défilement lorsque le contenu DIV dépasse la zone de l'élément de conteneur. (Pas de démonstration, veuillez le copier directement sur Firebug) // d = document // w = fenêtre // $ = jQuery var contentArea = $ (this), wintop = contentArea.scrollTop (), docheight = $ (d) .height (), winheight = $ (w) .height (), divheight = $ ('# c


Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Dreamweaver CS6
Outils de développement Web visuel

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Adaptateur de serveur SAP NetWeaver pour Eclipse
Intégrez Eclipse au serveur d'applications SAP NetWeaver.

mPDF
mPDF est une bibliothèque PHP qui peut générer des fichiers PDF à partir de HTML encodé en UTF-8. L'auteur original, Ian Back, a écrit mPDF pour générer des fichiers PDF « à la volée » depuis son site Web et gérer différentes langues. Il est plus lent et produit des fichiers plus volumineux lors de l'utilisation de polices Unicode que les scripts originaux comme HTML2FPDF, mais prend en charge les styles CSS, etc. et présente de nombreuses améliorations. Prend en charge presque toutes les langues, y compris RTL (arabe et hébreu) et CJK (chinois, japonais et coréen). Prend en charge les éléments imbriqués au niveau du bloc (tels que P, DIV),

Télécharger la version Mac de l'éditeur Atom
L'éditeur open source le plus populaire
