Maison > interface Web > js tutoriel > Techniques avancées React SSR avec streaming et données dynamiques

Techniques avancées React SSR avec streaming et données dynamiques

Susan Sarandon
Libérer: 2025-01-05 03:49:38
original
223 Les gens l'ont consulté

Advanced React SSR Techniques with Streaming and Dynamic Data

À 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

Le

Le 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 />, options)
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

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={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <Suspense fallback='Loading...'>
        <Card />
      </Suspense>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App
Copier après la connexion
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

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) }
Copier après la connexion
Copier après la connexion

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)
    }
  })
}
Copier après la connexion
Copier après la connexion

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)
    }
  })
}
Copier après la connexion

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()
Copier après la connexion

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

Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal