Maison >interface Web >js tutoriel >Création d'applications SSR React prêtes pour la production
Dans un monde où chaque milliseconde compte, le rendu côté serveur est devenu une fonctionnalité essentielle pour les applications frontend.
Ce guide vous guidera à travers les modèles fondamentaux pour créer un SSR prêt pour la production avec React. Vous comprendrez les principes qui sous-tendent les frameworks basés sur React avec SSR intégré (comme Next.js) et apprendrez à créer vos propres solutions personnalisées.
Le code fourni est prêt pour la production, avec un processus de construction complet pour les parties client et serveur, y compris un Dockerfile. Dans cette implémentation, Vite est utilisé pour créer le code client et SSR, mais vous pouvez utiliser tout autre outil de votre choix. Vite permet également le rechargement à chaud pendant le mode développement pour le client.
Si vous êtes intéressé par une version de cette configuration sans Vite, n'hésitez pas à nous contacter.
Le rendu côté serveur (SSR) est une technique de développement Web où le serveur génère le contenu HTML d'une page Web avant de l'envoyer au navigateur. Contrairement au rendu côté client (CSR) traditionnel, où JavaScript crée le contenu sur l'appareil de l'utilisateur après avoir chargé un shell HTML vide, SSR fournit un HTML entièrement rendu directement depuis le serveur.
Principaux avantages de la RSS :
Le flux de votre application avec SSR suit ces étapes :
Je préfère utiliser les modèles Pnpm et React-swc-ts Vite, mais vous pouvez choisir n'importe quelle autre configuration.
pnpm create vite react-ssr-app --template react-swc-ts
Installer les dépendances :
pnpm create vite react-ssr-app --template react-swc-ts
Dans une application React typique, il existe un seul point d'entrée main.tsx pour index.html. Avec SSR, vous avez besoin de deux points d'entrée : un pour le serveur et un pour le client.
Le serveur Node.js exécutera votre application et générera le HTML en rendant vos composants React sous forme de chaîne (renderToString).
pnpm install
Le navigateur hydratera le HTML généré par le serveur, en le connectant au JavaScript pour rendre la page interactive.
L'hydratation est le processus d'attachement d'écouteurs d'événements et d'autres comportements dynamiques au HTML statique rendu par le serveur.
// ./src/entry-server.tsx import { renderToString } from 'react-dom/server' import App from './App' export function render() { return renderToString(<App />) }
Mettez à jour le fichier index.html à la racine de votre projet. Le l'espace réservé est l'endroit où le serveur injectera le HTML généré.
// ./src/entry-client.tsx import { hydrateRoot } from 'react-dom/client' import { StrictMode } from 'react' import App from './App' import './index.css' hydrateRoot( document.getElementById('root')!, <StrictMode> <App /> </StrictMode>, )
Toutes les dépendances requises pour le serveur doivent être installées en tant que dépendances de développement (devDependencies) pour garantir qu'elles ne sont pas incluses dans le bundle client.
Ensuite, créez un dossier à la racine de votre projet nommé ./server et ajoutez les fichiers suivants.
Réexportez le fichier du serveur principal. Cela rend l'exécution de commandes plus pratique.
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + React + TS</title> </head> <body> <div> <h3> Create Server </h3> <p>First, install the dependencies:<br> </p> <pre class="brush:php;toolbar:false">pnpm install -D express compression sirv tsup vite-node nodemon @types/express @types/compression
La constante HTML_KEY doit correspondre au commentaire d'espace réservé dans index.html. D'autres constantes gèrent les paramètres d'environnement.
// ./server/index.ts export * from './app'
Configurez un serveur Express avec différentes configurations pour les environnements de développement et de production.
// ./server/constants.ts export const NODE_ENV = process.env.NODE_ENV || 'development' export const APP_PORT = process.env.APP_PORT || 3000 export const PROD = NODE_ENV === 'production' export const HTML_KEY = `<!--app-html-->`
En développement, utilisez le middleware de Vite pour gérer les requêtes et transformer dynamiquement le fichier index.html avec rechargement à chaud. Le serveur chargera l'application React et la restituera au format HTML à chaque requête.
// ./server/app.ts import express from 'express' import { PROD, APP_PORT } from './constants' import { setupProd } from './prod' import { setupDev } from './dev' export async function createServer() { const app = express() if (PROD) { await setupProd(app) } else { await setupDev(app) } app.listen(APP_PORT, () => { console.log(`http://localhost:${APP_PORT}`) }) } createServer()
En production, utilisez la compression pour optimiser les performances, sirv pour servir les fichiers statiques et le bundle de serveurs prédéfini pour restituer l'application.
// ./server/dev.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import { HTML_KEY } from './constants' const HTML_PATH = path.resolve(process.cwd(), 'index.html') const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx') export async function setupDev(app: Application) { // Create a Vite development server in middleware mode const vite = await ( await import('vite') ).createServer({ root: process.cwd(), server: { middlewareMode: true }, appType: 'custom', }) // Use Vite middleware for serving files app.use(vite.middlewares) app.get('*', async (req, res, next) => { try { // Read and transform the HTML file let html = fs.readFileSync(HTML_PATH, 'utf-8') html = await vite.transformIndexHtml(req.originalUrl, html) // Load the entry-server.tsx module and render the app const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH) const appHtml = await render() // Replace the placeholder with the rendered HTML html = html.replace(HTML_KEY, appHtml) res.status(200).set({ 'Content-Type': 'text/html' }).end(html) } catch (e) { // Fix stack traces for Vite and handle errors vite.ssrFixStacktrace(e as Error) console.error((e as Error).stack) next(e) } }) }
Pour suivre les meilleures pratiques de création de votre application, vous devez exclure tous les packages inutiles et inclure uniquement ce que votre application utilise réellement.
Mettez à jour votre configuration Vite pour optimiser le processus de build et gérer les dépendances SSR :
// ./server/prod.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import compression from 'compression' import sirv from 'sirv' import { HTML_KEY } from './constants' 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') export async function setupProd(app: Application) { // Use compression for responses app.use(compression()) // Serve static files from the client build folder app.use(sirv(CLIENT_PATH, { extensions: [] })) app.get('*', async (_, res, next) => { try { // Read the pre-built HTML file let html = fs.readFileSync(HTML_PATH, 'utf-8') // Import the server-side render function and generate HTML const { render } = await import(ENTRY_SERVER_PATH) const appHtml = await render() // Replace the placeholder with the rendered HTML html = html.replace(HTML_KEY, appHtml) res.status(200).set({ 'Content-Type': 'text/html' }).end(html) } catch (e) { // Log errors and pass them to the error handler console.error((e as Error).stack) next(e) } }) }
Mettez à jour votre tsconfig.json pour inclure les fichiers du serveur et configurez TypeScript de manière appropriée :
pnpm create vite react-ssr-app --template react-swc-ts
Utilisez tsup, un bundler TypeScript, pour créer le code du serveur. L'option noExternal spécifie les packages à regrouper avec le serveur. Assurez-vous d'inclure tous les packages supplémentaires utilisés par votre serveur.
pnpm install
// ./src/entry-server.tsx import { renderToString } from 'react-dom/server' import App from './App' export function render() { return renderToString(<App />) }
Développement : Utilisez la commande suivante pour démarrer l'application avec rechargement à chaud :
// ./src/entry-client.tsx import { hydrateRoot } from 'react-dom/client' import { StrictMode } from 'react' import App from './App' import './index.css' hydrateRoot( document.getElementById('root')!, <StrictMode> <App /> </StrictMode>, )
Production : Créez l'application et démarrez le serveur de production :
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + React + TS</title> </head> <body> <div> <h3> Create Server </h3> <p>First, install the dependencies:<br> </p> <pre class="brush:php;toolbar:false">pnpm install -D express compression sirv tsup vite-node nodemon @types/express @types/compression
Pour vérifier que SSR fonctionne, vérifiez la première requête réseau adressée à votre serveur. La réponse doit contenir le HTML entièrement rendu de votre application.
Pour ajouter différentes pages à votre application, vous devez configurer correctement le routage et le gérer dans les points d'entrée client et serveur.
// ./server/index.ts export * from './app'
Enveloppez votre application avec BrowserRouter dans le point d'entrée client pour activer le routage côté client.
// ./server/constants.ts export const NODE_ENV = process.env.NODE_ENV || 'development' export const APP_PORT = process.env.APP_PORT || 3000 export const PROD = NODE_ENV === 'production' export const HTML_KEY = `<!--app-html-->`
Utilisez StaticRouter dans le point d'entrée du serveur pour gérer le routage côté serveur. Transmettez l'URL comme accessoire pour afficher l'itinéraire correct en fonction de la demande.
// ./server/app.ts import express from 'express' import { PROD, APP_PORT } from './constants' import { setupProd } from './prod' import { setupDev } from './dev' export async function createServer() { const app = express() if (PROD) { await setupProd(app) } else { await setupDev(app) } app.listen(APP_PORT, () => { console.log(`http://localhost:${APP_PORT}`) }) } createServer()
Mettez à jour les configurations de votre serveur de développement et de production pour transmettre l'URL de la requête à la fonction de rendu :
// ./server/dev.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import { HTML_KEY } from './constants' const HTML_PATH = path.resolve(process.cwd(), 'index.html') const ENTRY_SERVER_PATH = path.resolve(process.cwd(), 'src/entry-server.tsx') export async function setupDev(app: Application) { // Create a Vite development server in middleware mode const vite = await ( await import('vite') ).createServer({ root: process.cwd(), server: { middlewareMode: true }, appType: 'custom', }) // Use Vite middleware for serving files app.use(vite.middlewares) app.get('*', async (req, res, next) => { try { // Read and transform the HTML file let html = fs.readFileSync(HTML_PATH, 'utf-8') html = await vite.transformIndexHtml(req.originalUrl, html) // Load the entry-server.tsx module and render the app const { render } = await vite.ssrLoadModule(ENTRY_SERVER_PATH) const appHtml = await render() // Replace the placeholder with the rendered HTML html = html.replace(HTML_KEY, appHtml) res.status(200).set({ 'Content-Type': 'text/html' }).end(html) } catch (e) { // Fix stack traces for Vite and handle errors vite.ssrFixStacktrace(e as Error) console.error((e as Error).stack) next(e) } }) }
Avec ces modifications, vous pouvez désormais créer des itinéraires dans votre application React entièrement compatibles avec SSR. Cependant, cette approche de base ne gère pas les composants chargés paresseux (React.lazy). Pour gérer les modules chargés paresseux, veuillez vous référer à mon autre article, Techniques React SSR avancées avec streaming et données dynamiques, lié en bas.
Voici un Dockerfile pour conteneuriser votre application :
// ./server/prod.ts import { Application } from 'express' import fs from 'fs' import path from 'path' import compression from 'compression' import sirv from 'sirv' import { HTML_KEY } from './constants' 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') export async function setupProd(app: Application) { // Use compression for responses app.use(compression()) // Serve static files from the client build folder app.use(sirv(CLIENT_PATH, { extensions: [] })) app.get('*', async (_, res, next) => { try { // Read the pre-built HTML file let html = fs.readFileSync(HTML_PATH, 'utf-8') // Import the server-side render function and generate HTML const { render } = await import(ENTRY_SERVER_PATH) const appHtml = await render() // Replace the placeholder with the rendered HTML html = html.replace(HTML_KEY, appHtml) res.status(200).set({ 'Content-Type': 'text/html' }).end(html) } catch (e) { // Log errors and pass them to the error handler console.error((e as Error).stack) next(e) } }) }
Créer et exécuter l'image Docker
// ./vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' import { dependencies } from './package.json' export default defineConfig(({ mode }) => ({ plugins: [react()], ssr: { noExternal: mode === 'production' ? Object.keys(dependencies) : undefined, }, }))
{ "include": [ "src", "server", "vite.config.ts" ] }
Dans ce guide, nous avons établi une base solide pour créer des applications SSR prêtes pour la production avec React. Vous avez appris à configurer le projet, à configurer le routage et à créer un Dockerfile. Cette configuration est idéale pour créer efficacement des pages de destination ou des petites applications.
Cela fait partie de ma série sur la RSS avec React. Restez à l'écoute pour plus d'articles !
Je suis toujours ouvert aux commentaires, à la collaboration ou aux discussions sur des idées techniques – n'hésitez pas à nous contacter !
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!