本部落格探討如何利用 React 的最新功能,使用最少的工具和框架創建輕量級、靈活的 React 應用程式。雖然像 Next.js 和 Remix 這樣的框架非常適合許多用例,但本指南適合有興趣在保留類似功能的同時擁有完全控制權的人。
隨著React 19 的發布及其支援SSR 的新功能,我決定嘗試使用最少的工具創建一個支援SSR 的React 應用程序,但首先讓我們探索Next.js 提供的我們需要考慮的基本功能:
Feature | Next.js |
---|---|
SSR (Server-Side Rendering) | Built-in with minimal setup. |
SSG (Static Site Generation) | Built-in with getStaticProps. |
Routing | File-based routing. |
Code Splitting | Automatic. |
Image Optimization | Built-in with next/image. |
Performance Optimizations | Automatic (e.g., prefetching, critical CSS). |
SEO | Built-in tools like next/head. |
基於這些功能,我們將創建一個最小的設定;這是逐步指南:
注意:您可以在我的倉庫中找到本教程的內容 https://github.com/willyelm/react-app
作為設定的先決條件,我們需要安裝 Node.js 和以下軟體包:
我們的設定將使用 Express 處理路由,以提供靜態檔案、公用檔案和 REST API。然後使用react-router-dom處理所有請求。一旦載入到瀏覽器中,我們的客戶端捆綁包將水合我們的預渲染內容並使我們的元件具有互動性。這是這個想法的圖表表示:
該圖說明了 Express 應用程式如何透過在伺服器上預先渲染 React 元件並傳回 HTML 來處理請求。在客戶端,React 水合這些預渲染元件以實現互動性。
記住這個圖,讓我們建立一個資料夾結構:
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json
讓我們新增依賴項並設定 package.json:
{ "name": "react-app", "type": "module", "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "esbuild": "^0.24.2", "typescript": "^5.7.2" }, "dependencies": { "express": "^4.21.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" }, "scripts": { "build": "node scripts/build", "dev": "node scripts/dev", "start": "node dist/main.js" } }
注意:「type」:「module」屬性是允許node.js執行ESM腳本所必需的。
由於我們將使用 Typescript,因此我們將配置一個 tsconfig.json 檔案:
{ "compilerOptions": { "esModuleInterop": true, "verbatimModuleSyntax": true, "noEmit": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "lib": ["DOM", "DOM.Iterable", "ES2022"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", "baseUrl": ".", "paths": { "src": [ "./src/" ] } }, "include": [ "src" ], "exclude": [ "node_modules" ] }
為什麼要用 esbuild?與其他工具相比,esbuild 保持了最小化,它非常快(迄今為止最快的捆綁器)並且預設支援 typescript 和 esm。
在此設定中,我們將使用 esbuild 建立我們的開發和建置腳本,並轉譯客戶端和伺服器套件。在本節中,我們將在工作區的腳本資料夾中工作。
scripts/config.js:此檔案將包含客戶端和伺服器套件的基本配置,該配置將為我們的腳本共用。
import path from 'node:path'; // Working dir const workspace = process.cwd(); // Server bundle configuration export const serverConfig = { bundle: true, platform: 'node', format: 'esm', // Support esm packages packages: 'external', // Omit node packages from our node bundle logLevel: 'error', sourcemap: 'external', entryPoints: { main: path.join(workspace, 'src', 'main.tsx') // Express app }, tsconfig: path.join(workspace, 'tsconfig.json'), outdir: path.join(workspace, 'dist') }; // Client bundle configuration export const clientConfig = { bundle: true, platform: 'browser', format: 'esm', sourcemap: 'external', logLevel: 'error', tsconfig: path.join(workspace, 'tsconfig.json'), entryPoints: { index: path.join(workspace, 'src', 'index.tsx'), // Client react app style: path.join(workspace, 'src', 'style.css') // Stylesheet }, outdir: path.join(workspace, 'dist', 'static'), // Served as /static by express };
scripts/dev.js:此腳本捆綁了客戶端和伺服器應用程序,並在監視模式下運行主伺服器腳本。
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json
使用此腳本,我們應該能夠按照工作區中 package.json 中的配置運行 npm run dev。
scripts/build.js:與 dev 類似,但我們只需要啟用 minify。
{ "name": "react-app", "type": "module", "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "esbuild": "^0.24.2", "typescript": "^5.7.2" }, "dependencies": { "express": "^4.21.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.1.0" }, "scripts": { "build": "node scripts/build", "dev": "node scripts/dev", "start": "node dist/main.js" } }
此腳本將透過執行 npm run build 並使用 npm start 執行應用程式來產生準備用於生產的 dist 套件。
現在我們已經將 esbuild 配置為捆綁我們的節點和客戶端應用程序,讓我們開始創建一個 Express 伺服器並實現 React SSR。
這是一個簡單的應用程序,使用 Express 靜態和中間件方法來提供靜態檔案、處理伺服器路由以及使用 React-router-dom 進行路由。
src/main.tsx:這是主要的 Node.js 應用程序,用於初始化伺服器、使用 Express 處理路由並實作 React SSR。
{ "compilerOptions": { "esModuleInterop": true, "verbatimModuleSyntax": true, "noEmit": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "lib": ["DOM", "DOM.Iterable", "ES2022"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", "baseUrl": ".", "paths": { "src": [ "./src/" ] } }, "include": [ "src" ], "exclude": [ "node_modules" ] }
React應用程式將使用react-router-dom處理路由,我們的應用程式將包含一個主頁和一個NotFound頁面來測試水合作用,我們將在主頁上新增一個計數器按鈕並利用React 19,我們將更新元標籤標題和描述。
src/App/Home.tsx:一個非常小的 FunctionComponent。
import path from 'node:path'; // Working dir const workspace = process.cwd(); // Server bundle configuration export const serverConfig = { bundle: true, platform: 'node', format: 'esm', // Support esm packages packages: 'external', // Omit node packages from our node bundle logLevel: 'error', sourcemap: 'external', entryPoints: { main: path.join(workspace, 'src', 'main.tsx') // Express app }, tsconfig: path.join(workspace, 'tsconfig.json'), outdir: path.join(workspace, 'dist') }; // Client bundle configuration export const clientConfig = { bundle: true, platform: 'browser', format: 'esm', sourcemap: 'external', logLevel: 'error', tsconfig: path.join(workspace, 'tsconfig.json'), entryPoints: { index: path.join(workspace, 'src', 'index.tsx'), // Client react app style: path.join(workspace, 'src', 'style.css') // Stylesheet }, outdir: path.join(workspace, 'dist', 'static'), // Served as /static by express };
src/App/NotFound.tsx:找不到頁面時的預設 FunctionComponent。
import { spawn } from 'node:child_process'; import path from 'node:path'; import { context } from 'esbuild'; import { serverConfig, clientConfig } from './config.js'; // Working dir const workspace = process.cwd(); // Dev process async function dev() { // Build server in watch mode const serverContext = await context(serverConfig); serverContext.watch(); // Build client in watch mode const clientContext = await context(clientConfig); clientContext.watch(); // Run server const childProcess = spawn('node', [ '--watch', path.join(workspace, 'dist', 'main.js') ], { stdio: 'inherit' }); // Kill child process on program interruption process.on('SIGINT', () => { if (childProcess) { childProcess.kill(); } process.exit(0); }); } // Start the dev process dev();
src/App/App.tsx:使用react-router-dom設定我們的應用程式。
import { build } from 'esbuild'; import { clientConfig, serverConfig } from './config.js'; // build process async function bundle() { // Build server await build({ ...serverConfig, minify: true }); // Build client await build({ ...clientConfig, minify: true }); } // Start the build process bundle();
最後,設定了 esbuild 腳本,設定了 Express 伺服器,並實作了 React SSR。我們可以運行我們的伺服器:
import path from 'node:path'; import express, { type Request, type Response } from 'express'; import { renderToPipeableStream } from 'react-dom/server'; import { StaticRouter } from 'react-router'; import { App } from './App/App'; const app = express(); // Create Express App const port = 3000; // Port to listen const workspace = process.cwd(); // workspace // Serve static files like js bundles and css files app.use('/static', express.static(path.join(workspace, 'dist', 'static'))); // Server files from the /public folder app.use(express.static(path.join(workspace, 'public'))); // Fallback to render the SSR react app app.use((request: Request, response: Response) => { // React SSR rendering as a stream const { pipe } = renderToPipeableStream( <html lang="en"> <head> <meta charSet="UTF-8" /> <link rel='stylesheet' href={`/static/style.css`} /> </head> <body> <base href="/" /> <div> <p>src/index.tsx: On the client side to activate our components and make them interactive we need to "hydrate".<br> </p> <pre class="brush:php;toolbar:false">import { hydrateRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router'; import { App } from './App/App'; // Hydrate pre-renderer #app element hydrateRoot( document.getElementById('app') as HTMLElement, <BrowserRouter> <App /> </BrowserRouter> );
這將顯示一則訊息 [app] 正在連接埠 3000 上偵聽。導航到
http://localhost:3000 來看看。
測試 SSR 和 SEO 元標記:
您也可以在我的倉庫 willyelm/react-app 中找到本教學的原始碼
專案利用了react@19、react-router-dom@7等的最新功能來設定SSR。
react-app/ # This will be our workspace directory. - public/ - scripts/ - build.js # Bundle our server and client scripts. - config.js # esbuild config to bundle. - dev.js # Bundle on watch mode and run server. - src/ - App/ # Our components will be here. - App.tsx # The main application with browser routing. - Home.tsx. # Our default page component. - NotFound.tsx # Fallback page for unmatched routes. - index.tsx # Hydrate our pre-rendered client app. - main.tsx # Server app with SSR components. - style.css # Initial stylesheet. package.json tsconfig.json…
在本指南中,我們使用 esbuild 和 Express 建立了一個帶有 SSR 的 React 應用程序,重點關注最小且靈活的設定。使用 React 19、React Router DOM 7 和 esbuild 等現代化工具,我們實現了快速且有效率的工作流程,同時避免了大型框架的開銷。
我們可以增強此實現,包括以下內容:
感謝您的閱讀並祝您編碼愉快。
以上是使用 React 和 esbuild 實作 SSR 的簡單方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!