通过服务端渲染(SSR)我们可以将应用的 HTML、JavaScript、CSS 代码,甚至是应用的初始状态数据在首次请求时就返回给浏览器。浏览器在拿到数据后,即使在 JavaScript 被禁用的状态下也能正确的展示页面。除此以外,服务端渲染还可以帮助实现搜索引擎优化(SEO)。

我们现在来展示一下如何使用 SSR。

添加 Babel

通常来说,在 React 中我们都会使用 JSX,因此,需要先配置 babel。

npm add babel-loader @babel/core @babel/preset-react --develop
1

.babelrc

{
  "presets": [
    ["@babel/preset-env", { "modules": false }],
    "@babel/preset-react"
  ]
}
1
2
3
4
5
6

添加 React demo

安装 react 包。

npm add react react-dom
1

做服务端渲染,在应用入口处有两种情况需要考虑。在浏览器端渲染时,我们需要将应用渲染到页面上。而在服务端时,我们只需要返回应用的 JSX 代码。

另外,ES2016 的导入导出语法与 CommonJS 的导入导出预发不能混用,我们需要在入口代码处统一使用 CommonJS 语法。

src/ssr.js

const React = require('react');
const ReactDOM = require('react-dom');
const SSR = <div onClick={() => alert('hello')}>Hello world</div>;

if (typeof document === 'undefined') {
  module.exports = SSR;
} else {
  // 浏览器端做渲染
  ReactDOM.hydrate(SSR, document.getElementById('app'));
}
1
2
3
4
5
6
7
8
9
10

配置 Webpack

我们单独定义一份配置文件。假设我们需要在多种环境中使用相同的输出,则使用 UMD 进行输出更为合理。

webpack.ssr.js

const path = require('path');
const APP_SOURCE = path.join(__dirname, 'src');

module.exports = {
  mode: 'production',
  entry: { index: path.join(APP_SOURCE, 'ssr.js') },
  output: {
    path: path.join(__dirname, 'static'),
    filename: '[name].js',
    libraryTarget: 'umd',
    globalObject: 'this',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: APP_SOURCE,
        use: 'babel-loader',
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

添加构建脚本.

package.json

{
  "scripts": {
    "build:ssr": "wp --config webpack.ssr.js"
  }
}
1
2
3
4
5

此时执行 npm run build:ssr,我们可以看到新构建出来的文件 ./static/index.js。下一步就是搭建一个服务来渲染页面。

搭建服务

我们使用 express 来举例。

npm add express --develop
1

server.js

const express = require('express');
const { renderToString } = require('react-dom/server');
const SSR = require('./static');

const app = express();
app.use(express.static('static'));
app.get('/', (req, res) =>
  res.status(200).send(renderMarkup(renderToString(SSR)))
);
app.listen(process.env.PORT || 8080);

function renderMarkup(html) {
  return `<!DOCTYPE html>
<html>
  <head><title>SSR Demo</title><meta charset="utf-8" /></head>
  <body>
    <div id="app">${html}</div>
    <script src="./index.js"></script>
  </body>
</html>`;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

启动服务,node ./server.js,访问 http://localhost:8080 就可以看到调试工具的 Source 标签页中看到服务端渲染返回的内容了。

此时如果修改了应用代码,刷新页面数据不会更新。我们可以通过 webpack-dev-middlewareopen in new window 来解决这个问题。

上面这是简单演示了一个服务端渲染的 demo。有很多实际的问题没有解决,比如如何加载一个非 JavaScript 模块,像 CSS、图片等直接在服务端加载会导致报错。社区有很多成熟的服务端渲染框架,比如 Next.js 可以帮助解决这些问题。

预渲染(Prerendering)

预渲染同样可以解决 SEO 问题。预渲染通过一个无头浏览器将页面内容渲染完成后提供给爬虫。相对于服务端渲染来说,预渲染更易实现。预渲染也有缺点,就是对频繁变化的数据支持的不够理想。

在 Webpack 中,我们可以使用这些工具来实现预渲染。

总结

服务端渲染可以为浏览器在加载初始 JavaScript 的时候提供更多的展示内容,同时也带来了更大的技术挑战。Webpack 可以为服务端渲染提供更多的资源构建支持。在一些主流的服务端渲染框架中,比如 Next.js 中都内置了 Webpack 来构建资源。

关注微信公众号,获取最新推送~