Heim >Web-Frontend >js-Tutorial >Erstellen produktionsbereiter SSR-Reaktionsanwendungen
In einer Welt, in der jede Millisekunde zählt, ist serverseitiges Rendering zu einer wesentlichen Funktion für Frontend-Anwendungen geworden.
Dieser Leitfaden führt Sie durch die grundlegenden Muster zum Aufbau eines produktionsbereiten SSR mit React. Sie erhalten ein Verständnis für die Prinzipien hinter React-basierten Frameworks mit integriertem SSR (wie Next.js) und lernen, wie Sie Ihre eigenen benutzerdefinierten Lösungen erstellen.
Der bereitgestellte Code ist produktionsbereit und umfasst einen vollständigen Build-Prozess für Client- und Serverteile, einschließlich einer Docker-Datei. In dieser Implementierung wird Vite zum Erstellen des Client- und SSR-Codes verwendet, Sie können jedoch auch andere Tools Ihrer Wahl verwenden. Vite ermöglicht dem Client auch Hot-Reloading während des Entwicklungsmodus.
Wenn Sie an einer Version dieses Setups ohne Vite interessiert sind, können Sie sich gerne an uns wenden.
Serverseitiges Rendering (SSR) ist eine Technik in der Webentwicklung, bei der der Server den HTML-Inhalt einer Webseite generiert, bevor er ihn an den Browser sendet. Im Gegensatz zum herkömmlichen clientseitigen Rendering (CSR), bei dem JavaScript den Inhalt auf dem Gerät des Benutzers erstellt, nachdem eine leere HTML-Shell geladen wurde, liefert SSR vollständig gerendertes HTML direkt vom Server.
Hauptvorteile von SSR:
Der Ablauf Ihrer App mit SSR folgt diesen Schritten:
Ich bevorzuge die Vite-Vorlage „pnpm“ und „react-swc-ts“, aber Sie können jedes andere Setup wählen.
pnpm create vite react-ssr-app --template react-swc-ts
Installieren Sie die Abhängigkeiten:
pnpm create vite react-ssr-app --template react-swc-ts
In einer typischen React-Anwendung gibt es einen einzigen main.tsx-Einstiegspunkt für index.html. Bei SSR benötigen Sie zwei Einstiegspunkte: einen für den Server und einen für den Client.
Der Node.js-Server führt Ihre App aus und generiert den HTML-Code, indem er Ihre React-Komponenten in einen String (renderToString) rendert.
pnpm install
Der Browser hydratisiert das vom Server generierte HTML und verbindet es mit dem JavaScript, um die Seite interaktiv zu machen.
Hydration ist der Prozess des Anhängens von Ereignis-Listenern und anderen dynamischen Verhaltensweisen an den vom Server gerenderten statischen HTML-Code.
// ./src/entry-server.tsx import { renderToString } from 'react-dom/server' import App from './App' export function render() { return renderToString(<App />) }
Aktualisieren Sie die Datei index.html im Stammverzeichnis Ihres Projekts. Das Der Platzhalter ist der Ort, an dem der Server den generierten HTML-Code einfügt.
// ./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>, )
Alle für den Server erforderlichen Abhängigkeiten sollten als Entwicklungsabhängigkeiten (devDependencies) installiert werden, um sicherzustellen, dass sie nicht im Client-Bundle enthalten sind.
Als nächstes erstellen Sie im Stammverzeichnis Ihres Projekts einen Ordner mit dem Namen ./server und fügen die folgenden Dateien hinzu.
Exportieren Sie die Hauptserverdatei erneut. Dadurch wird das Ausführen von Befehlen komfortabler.
<!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
Die HTML_KEY-Konstante muss mit dem Platzhalterkommentar in index.html übereinstimmen. Andere Konstanten verwalten Umgebungseinstellungen.
// ./server/index.ts export * from './app'
Richten Sie einen Express-Server mit unterschiedlichen Konfigurationen für Entwicklungs- und Produktionsumgebungen ein.
// ./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-->`
Verwenden Sie in der Entwicklung die Middleware von Vite, um Anfragen zu verarbeiten und die Datei index.html durch Hot-Reload dynamisch umzuwandeln. Der Server lädt die React-Anwendung und rendert sie bei jeder Anfrage in HTML.
// ./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()
Verwenden Sie in der Produktion die Komprimierung, um die Leistung zu optimieren, sirv, um statische Dateien bereitzustellen, und das vorgefertigte Serverpaket, um die App zu rendern.
// ./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) } }) }
Um die Best Practices für die Erstellung Ihrer Anwendung zu befolgen, sollten Sie alle unnötigen Pakete ausschließen und nur das einschließen, was Ihre Anwendung tatsächlich verwendet.
Aktualisieren Sie Ihre Vite-Konfiguration, um den Build-Prozess zu optimieren und SSR-Abhängigkeiten zu verarbeiten:
// ./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) } }) }
Aktualisieren Sie Ihre tsconfig.json, um die Serverdateien einzuschließen, und konfigurieren Sie TypeScript entsprechend:
pnpm create vite react-ssr-app --template react-swc-ts
Verwenden Sie tsup, einen TypeScript-Bundler, um den Servercode zu erstellen. Die Option noExternal gibt die Pakete an, die mit dem Server gebündelt werden sollen. Stellen Sie sicher, dass Sie alle zusätzlichen Pakete einschließen, die Ihr Server verwendet.
pnpm install
// ./src/entry-server.tsx import { renderToString } from 'react-dom/server' import App from './App' export function render() { return renderToString(<App />) }
Entwicklung: Verwenden Sie den folgenden Befehl, um die Anwendung mit Hot-Neuladen zu starten:
// ./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>, )
Produktion: Erstellen Sie die Anwendung und starten Sie den Produktionsserver:
<!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
Um zu überprüfen, ob SSR funktioniert, überprüfen Sie die erste Netzwerkanfrage an Ihren Server. Die Antwort sollte den vollständig gerenderten HTML-Code Ihrer Anwendung enthalten.
Um Ihrer App verschiedene Seiten hinzuzufügen, müssen Sie das Routing richtig konfigurieren und es sowohl in Client- als auch in Server-Einstiegspunkten verarbeiten.
// ./server/index.ts export * from './app'
Umschließen Sie Ihre Anwendung mit BrowserRouter im Client-Einstiegspunkt, um clientseitiges Routing zu ermöglichen.
// ./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-->`
Verwenden Sie StaticRouter im Server-Einstiegspunkt, um das serverseitige Routing zu verwalten. Übergeben Sie die URL als Requisite, um die richtige Route basierend auf der Anfrage darzustellen.
// ./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()
Aktualisieren Sie sowohl Ihre Entwicklungs- als auch Ihre Produktionsserver-Setups, um die Anforderungs-URL an die Renderfunktion zu übergeben:
// ./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) } }) }
Mit diesen Änderungen können Sie jetzt Routen in Ihrer React-App erstellen, die vollständig mit SSR kompatibel sind. Dieser grundlegende Ansatz behandelt jedoch keine verzögert geladenen Komponenten (React.lazy). Informationen zur Verwaltung verzögert geladener Module finden Sie in meinem anderen Artikel, Erweiterte React SSR-Techniken mit Streaming und dynamischen Daten, der unten verlinkt ist.
Hier ist eine Docker-Datei zum Containerisieren Ihrer Anwendung:
// ./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) } }) }
Erstellen und Ausführen des Docker-Images
// ./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" ] }
In diesem Leitfaden haben wir eine solide Grundlage für die Erstellung produktionsbereiter SSR-Anwendungen mit React geschaffen. Sie haben gelernt, wie Sie das Projekt einrichten, das Routing konfigurieren und eine Docker-Datei erstellen. Dieses Setup ist ideal für die effiziente Erstellung von Landingpages oder kleinen Apps.
Dies ist Teil meiner Serie über SSR mit React. Bleiben Sie dran für weitere Artikel!
Ich bin immer offen für Feedback, Zusammenarbeit oder die Diskussion technischer Ideen – zögern Sie nicht, mich zu kontaktieren!
Das obige ist der detaillierte Inhalt vonErstellen produktionsbereiter SSR-Reaktionsanwendungen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!