1. 服务端渲染(SSR)的优缺点

1.1 优点

  • 首屏加载速度更快,无需等待加载JS资源更快的渲染内容,有更好的用户体验
  • 对SEO友好,方便搜索引擎爬取,提高入口流量
  • 方便使用接口转发代理,隐藏后端信息减少攻击

1.2 缺点

  • 对服务端性能消耗较高
  • 项目复杂度变高,多了node服务层
  • 需要增加SSR代码测兼容及其运维工作,增加了运维成本
  • 项目构建打包需要更多的时间,相较于CSR增加了50%左右

2. 具体实现

2.1 在umi加载中开启ssr

以下以umi3为例

  1. // 在.umirc.ts中添加配置
  2. ssr: {
  3. // devServerRender: false, // 如果开发时不开启,则设置为false,默认为开启
  4. },
  5. // ssr支持按需加载
  6. // dynamicImport: {}, // 开启按需加载(可选)

image.png
按照如上配置使用umi build构建后,dist目录中将多处一个umi.server.js文件,用于服务端实现SSR
image.png

2.2 兼容性处理

由于node环境与浏览器环境存在一些差异,需要对一些node环境中不兼容的部分进行处理。这里举几个例子

  • node中没有window对象,所有使用到window的地方(如window.location、window.origin)都要去掉window而使用全局变量
  • node中没有locaStorage对象,可以借助umi中的isBrower方法处理
  • node中没有self等全局对象…

2.3 服务端实现

umi不耦合服务端架构,在服务端我们可以使用express、koa、egg等架构来实现SSR,这里以express为例。

2.3.1 express安装与项目初始化

  1. # 这里采用express生成器
  2. npm install -g express-generator
  3. # 创建项目文件夹
  4. mkdir app_back_render
  5. cd app_back_render
  6. # 安装依赖
  7. yarn
  8. # 运行
  9. DEBUG=app_back_render:* npm start

初始化后目录如下

  1. .
  2. ├── app.js
  3. ├── bin
  4. └── www
  5. ├── package.json
  6. ├── public
  7. ├── images
  8. ├── javascripts
  9. └── stylesheets
  10. └── style.css
  11. ├── routes
  12. ├── index.js
  13. └── users.js
  14. └── views
  15. ├── error.pug
  16. ├── index.pug
  17. └── layout.pug

2.3.2 将umi打包文件部署到服务中

将umi打包后的dist文件复制到express项目根目录中,如下图所示。

image.png

在app.js中配置静态资源文件使得静态资源可被访问,内容如下

  1. var createError = require('http-errors');
  2. var express = require('express');
  3. var path = require('path');
  4. var cookieParser = require('cookie-parser');
  5. var logger = require('morgan');
  6. var app = express();
  7. // view engine setup
  8. app.set('views', path.join(__dirname, 'views'));
  9. app.set('view engine', 'jade');
  10. app.use(logger('dev'));
  11. app.use(express.json());
  12. app.use(express.urlencoded({ extended: false }));
  13. app.use(cookieParser());
  14. app.use(express.static(path.join(__dirname, 'dist'))); // 配置dist目录可被访问
  15. module.exports = app;

到这里,已经完成了CSR部署,启动Express服务(DEBUG=app_back_render:* npm start),即可访问项目

2.3.3 添加SSR支持

在app.js中添加如下代码。

  1. app.use(async (req, res, next) => {
  2. // 或者从 CDN 上下载到 server 端
  3. // const serverPath = await downloadServerBundle('http://cdn.com/bar/umi.server.js');
  4. const ext = path.extname(req.url);
  5. // 无需SSR转化文件直接返回给客户端,通过ext判断
  6. if(!ext) {
  7. const render = require('./dist/umi.server');
  8. res.setHeader('Content-Type', 'text/html');
  9. const context = {};
  10. // render 用法参照 https://umijs.org/zh-CN/docs/ssr
  11. const { html, error, rootContainer } = await render({
  12. // 有需要可带上 query
  13. path: req.url,
  14. context,
  15. // 可自定义 html 模板
  16. // htmlTemplate: defaultHtml,
  17. // 启用流式渲染
  18. // mode: 'stream',
  19. // html 片段静态标记(适用于静态站点生成)
  20. // staticMarkup: false,
  21. // 扩展 getInitialProps 在服务端渲染中的参数
  22. // getInitialPropsCtx: {},
  23. // manifest,正常情况下不需要
  24. });
  25. res.send(html);
  26. }else {
  27. next();
  28. }
  29. })

render参数与返回值介绍

参数:

  1. {
  2. // 渲染页面路由,支持 `base` 和带 query 的路由,通过 umi 配置
  3. path: string;
  4. // 可选,初始化数据,传透传到 getInitialProps 方法的参数中
  5. initialData?: object;
  6. // 可选,html 模板,这里可自定义模板,默认是用 umi 内置的 html
  7. htmlTemplate?: string;
  8. // 可选,页面内容挂载节点,与 htmlTemplate 配合使用,默认为 root
  9. mountElementId?: string;
  10. // 上下文数据,可用来标记服务端渲染页面时的状态
  11. context?: object
  12. // ${protocol}://${host} 扩展 location 对象
  13. origin?: string;
  14. }

返回值:

  1. {
  2. // html 内容,服务端渲染错误后,会返回原始 html
  3. html?: string | Stream;
  4. // 挂载节点中的渲染内容(ssr 渲染实际上只是渲染挂载节点中的内容),同时你也可以用该值来拼接自定义模板
  5. rootContainer: string | Stream;
  6. // 错误对象,服务端渲染错误后,值不为 null
  7. error?: Error;
  8. }

同样使用DEBUG=app_back_render:* npm start启动项目,启动后查看网页源代码,如果

DOM 里的元素不为空,则是 SSR,否则为 CSR。

另附koa和egg的官方示例
koa: https://github.com/umijs/umi/tree/master/examples/ssr-koa
egg: https://github.com/umijs/umi/tree/master/examples/ssr-with-eggjs

3. todo

  • 服务端不支持localStorage,而我们的项目用到了不少的localStorage,比如用户登录授权信息、直播页授权因反复跳转而使用的localStorage存储状态、微信支付弹窗localStorage状态等,这些可能会造成负面影响。
  • 微信授权使用了服务端设置cookie功能,这个方案对服务端渲染是否有效,还有待研究。
  • 虽然本项目关于SSR的兼容都处理了,但是仍然存在大量第三方插件的兼容性问题。