警告: 本教程旨在简要介绍ReactDOM.hydrate()
和ReactDOMServer.rendertoString()
。 它不适用于生产用途。
或者,Next.js 提供了一种现代方法来创建使用 React 构建的静态和服务器渲染应用程序。
介绍
服务器端渲染(SSR)是一种流行的技术,用于在服务器上渲染客户端单页应用程序(SPA),然后将完全渲染的页面发送到客户端。 这允许将动态组件用作静态 HTML 标记。
当索引无法正确处理 JavaScript 时,此方法可用于搜索引擎优化 (SEO)。 在下载大型 JavaScript 包因网络缓慢而受损的情况下,它也可能是有益的。
在本教程中,您将使用 Create React App 初始化 React 应用程序,然后修改项目以启用服务器端渲染。
在本教程结束时,您将拥有一个包含客户端 React 应用程序和服务器端 Express 应用程序的工作项目。
先决条件
要完成本教程,您需要:
- Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
本教程已使用 Node v16.13.1、npm
v8.1.2、react
v17.0.2、@babel/core
v7.16.0、webpack
v4.44.2、 express
v4.17.1、nodemon
v2.0.15 和 npm-run-all
v4.1.5。
第 1 步 - 创建 React 应用程序并修改应用程序组件
首先,使用 npx 使用最新版本的 Create React App 启动一个新的 React 应用程序。
让我们调用应用程序,react-ssr-example:
npx create-react-app react-ssr-example
然后,cd
进入新目录:
cd react-ssr-example
最后,启动新的客户端应用程序以验证安装:
npm start
您将看到浏览器窗口中显示的示例 React 应用程序。
现在,在 src
目录下,我们新建一个 <Home>
组件:
nano src/Home.js
接下来,将以下代码添加到 Home.js
文件中:
src/Home.js
function Home(props) { return <h1>Hello {props.name}!</h1>; }; export default Home;
这将创建一个带有指向名称的 "Hello"
消息的 <h1>
标题。
接下来,让我们在 <App>
组件中渲染 <Home>
。 打开src
目录下的App.js
文件:
nano src/App.js
然后,用这些新的代码行替换现有的代码行:
src/App.js
import Home from './Home'; function App() { return <Home name="Sammy"/>; } export default App;
这会将 name
传递到 <Home>
组件,因此您希望显示的消息是:
Output"Hello Sammy!"
在应用程序的 index.js
文件中,您将使用 ReactDOM 的 水化方法 而不是 render
向 DOM 渲染器指示您打算在服务器端渲染后重新水化应用程序.
让我们打开src
目录下的index.js
文件:
nano src/index.js
然后,将 index.js
文件的内容替换为以下代码:
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
客户端设置完毕,您可以继续设置服务器端。
第 2 步 — 创建 Express 服务器并渲染 App 组件
现在您已经有了应用程序,让我们设置一个服务器,该服务器将发送渲染版本。 您将使用 Express 作为服务器 。
注意: Create React App 的 react-scripts
安装 express
的版本作为 webpack-dev-server
的要求。 为避免依赖树冲突,本教程不再包含此安装步骤。
接下来,在项目的根目录下新建一个server
目录:
mkdir server
然后,在 server
目录中,创建一个包含 Express 服务器代码的新 index.js
文件:
nano server/index.js
添加需要的导入并定义一些常量:
服务器/index.js
import path from 'path'; import fs from 'fs'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import express from 'express'; import App from '../src/App'; const PORT = process.env.PORT || 3006; const app = express();
接下来,添加一些错误处理的服务器代码:
服务器/index.js
// ... app.get('/', (req, res) => { const app = ReactDOMServer.renderToString(<App />); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => { if (err) { console.error('Something went wrong:', err); return res.status(500).send('Oops, better luck next time!'); } return res.send( data.replace('<div id="root"></div>', `<div id="root">${app}</div>`) ); }); }); app.use(express.static('./build')); app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); });
可以直接从服务器从客户端应用程序导入 <App>
组件。
这里发生了三件重要的事情:
- Express 用于将
build
目录中的内容作为静态文件提供。 ReactDOMServer
的renderToString
用于将应用程序呈现为静态 HTML 字符串。- 读取来自已构建客户端应用程序的静态
index.html
文件。 应用程序的静态内容被注入到<div>
中,其中id
为"root"
。 这是作为对请求的响应发送的。
第三步——配置 webpack、Babel 和 npm
脚本
要使服务器代码正常工作,您需要使用 webpack 和 Babel 捆绑和转译它。 要做到这一点。
注意:本教程的早期版本安装了babel-core
、babel-preset-env
和babel-preset-react-app
。 此后,这些软件包已被存档,并改用了 mono repo 版本。
Create React App 的 react-scripts
处理以下包的安装: webpack
, webpack-cli
, webpack-node-externals
, @babel/core
, [X129X ]、@babel/preset-env
、@babel/preset-react
。 为避免依赖树冲突,本教程不再包含此安装步骤。
接下来,在项目的根目录下新建一个 Babel 配置文件:
nano .babelrc.json
然后,添加 env
和 react-app
预设:
.babelrc.json
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] }
注意: 本教程的早期版本使用 .babelrc
文件(没有 .json
文件扩展名)。 这是 Babel 6 的配置文件,但 Babel 7 不再是这种情况。
现在,为使用 Babel Loader 转译代码的服务器创建一个 webpack 配置。 首先在项目的根目录中创建 webpack.server.js
文件:
nano webpack.server.js
然后,将以下配置添加到 webpack.server.js
文件中:
webpack.server.js
const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: './server/index.js', target: 'node', externals: [nodeExternals()], output: { path: path.resolve('server-build'), filename: 'index.js' }, module: { rules: [ { test: /\.js$/, use: 'babel-loader' } ] } };
使用此配置,转译的服务器包将输出到名为 index.js
的文件中的 server-build
文件夹。
注意 webpack-node-externals
中的 target: 'node'
和 externals: [nodeExternals()]
的使用,这将省略捆绑包中 node_modules
中的文件; 服务器可以直接访问这些文件。
这样就完成了依赖安装以及webpack和babel的配置。
现在,重新访问 package.json
并添加帮助 npm
脚本:
nano package.json
将 dev:build-server
、dev:start
和 dev
脚本添加到 package.json
文件以构建和提供 SSR 应用程序:
包.json
"scripts": { "dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development -w", "dev:start": "nodemon ./server-build/index.js", "dev": "npm-run-all --parallel build dev:*", // ... },
dev:build-server
脚本将环境设置为 "development"
并使用您之前创建的配置文件调用 webpack
。 dev:start
脚本调用 nodemon
来提供构建的输出。
dev
脚本调用 npm-run-all
在 parallel
中运行 build
脚本和所有以 dev:*
开头的脚本 - 包括 [ X138X] 和 dev:start
。
注:无需修改package.json
文件。
nodemon
用于在进行更改时重新启动服务器。 npm-run-all
用于并行运行多个命令。
现在让我们通过在终端窗口中输入以下命令来安装这些软件包:
npm install nodemon@2.0.15 --save-dev npm install npm-run-all@4.1.5 --save-dev
有了这个,您可以运行以下命令来构建客户端应用程序,捆绑和转换服务器代码,并在 :3006
上启动服务器:
npm run dev
服务器 webpack 配置现在将监视更改,服务器将在更改时重新启动。 但是,对于客户端应用程序,每次进行更改时都需要手动重新构建。
注意:这里有一个未解决的问题。
现在,在您的网络浏览器中打开 http://localhost:3006/
并观察您的服务器端呈现的应用程序。
以前,查看源代码显示:
Output<div id="root"></div>
但是现在,随着您所做的更改,源代码显示:
Output<div id="root"><h1 data-reactroot="">Hello <!-- -->Sammy<!-- -->!</h1></div>
服务器端渲染成功将 <App>
组件转换为 HTML。
结论
在本教程中,您初始化了一个 React 应用程序并启用了服务器端渲染。
在这篇文章中,我们只是触及了可能的表面。 一旦路由、数据获取或 Redux 也需要成为服务器端渲染应用程序的一部分,事情就会变得更加复杂。
使用 SSR 的一个主要好处是拥有一个可以抓取其内容的应用程序,即使对于不执行 JavaScript 代码的抓取工具也是如此。 这有助于搜索引擎优化 (SEO) 并向社交媒体渠道提供元数据。
SSR 通常还可以帮助提高性能,因为在第一次请求时从服务器发送一个完全加载的应用程序。 对于非平凡的应用程序,您的里程可能会有所不同,因为 SSR 需要一个可能会变得有点复杂的设置,并且它会在服务器上产生更大的负载。 是否为您的 React 应用程序使用服务器端渲染取决于您的特定需求以及哪些权衡对您的用例最有意义。
如果您想了解有关 React 的更多信息,请查看我们的 如何在 React.js 中编码,或查看 我们的 React 主题页面 以了解练习和编程项目。