如何为React应用启用服务器端渲染

来自菜鸟教程
跳转至:导航、​搜索

警告: 本教程旨在简要介绍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 目录中的内容作为静态文件提供。
  • ReactDOMServerrenderToString 用于将应用程序呈现为静态 HTML 字符串。
  • 读取来自已构建客户端应用程序的静态 index.html 文件。 应用程序的静态内容被注入到 <div> 中,其中 id"root"。 这是作为对请求的响应发送的。

第三步——配置 webpack、Babel 和 npm 脚本

要使服务器代码正常工作,您需要使用 webpackBabel 捆绑和转译它。 要做到这一点。

注意:本教程的早期版本安装了babel-corebabel-preset-envbabel-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

然后,添加 envreact-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-serverdev:startdev 脚本添加到 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" 并使用您之前创建的配置文件调用 webpackdev:start 脚本调用 nodemon 来提供构建的输出。

dev 脚本调用 npm-run-allparallel 中运行 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 主题页面 以了解练习和编程项目。