1. 服务端渲染(SSR)的优缺点
1.1 优点
- 首屏加载速度更快,无需等待加载JS资源更快的渲染内容,有更好的用户体验
 - 对SEO友好,方便搜索引擎爬取,提高入口流量
 - 方便使用接口转发代理,隐藏后端信息减少攻击
 
1.2 缺点
- 对服务端性能消耗较高
 - 项目复杂度变高,多了node服务层
 - 需要增加SSR代码测兼容及其运维工作,增加了运维成本
 - 项目构建打包需要更多的时间,相较于CSR增加了50%左右
 
2. 具体实现
2.1 在umi加载中开启ssr
以下以umi3为例
// 在.umirc.ts中添加配置ssr: {// devServerRender: false, // 如果开发时不开启,则设置为false,默认为开启},// ssr支持按需加载// dynamicImport: {}, // 开启按需加载(可选)

按照如上配置使用umi build构建后,dist目录中将多处一个umi.server.js文件,用于服务端实现SSR
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安装与项目初始化
# 这里采用express生成器npm install -g express-generator# 创建项目文件夹mkdir app_back_rendercd app_back_render# 安装依赖yarn# 运行DEBUG=app_back_render:* npm start
初始化后目录如下
.├── app.js├── bin│ └── www├── package.json├── public│ ├── images│ ├── javascripts│ └── stylesheets│ └── style.css├── routes│ ├── index.js│ └── users.js└── views├── error.pug├── index.pug└── layout.pug
2.3.2 将umi打包文件部署到服务中
将umi打包后的dist文件复制到express项目根目录中,如下图所示。

在app.js中配置静态资源文件使得静态资源可被访问,内容如下
var createError = require('http-errors');var express = require('express');var path = require('path');var cookieParser = require('cookie-parser');var logger = require('morgan');var app = express();// view engine setupapp.set('views', path.join(__dirname, 'views'));app.set('view engine', 'jade');app.use(logger('dev'));app.use(express.json());app.use(express.urlencoded({ extended: false }));app.use(cookieParser());app.use(express.static(path.join(__dirname, 'dist'))); // 配置dist目录可被访问module.exports = app;
到这里,已经完成了CSR部署,启动Express服务(DEBUG=app_back_render:* npm start),即可访问项目
2.3.3 添加SSR支持
在app.js中添加如下代码。
app.use(async (req, res, next) => {// 或者从 CDN 上下载到 server 端// const serverPath = await downloadServerBundle('http://cdn.com/bar/umi.server.js');const ext = path.extname(req.url);// 无需SSR转化文件直接返回给客户端,通过ext判断if(!ext) {const render = require('./dist/umi.server');res.setHeader('Content-Type', 'text/html');const context = {};// render 用法参照 https://umijs.org/zh-CN/docs/ssrconst { html, error, rootContainer } = await render({// 有需要可带上 querypath: req.url,context,// 可自定义 html 模板// htmlTemplate: defaultHtml,// 启用流式渲染// mode: 'stream',// html 片段静态标记(适用于静态站点生成)// staticMarkup: false,// 扩展 getInitialProps 在服务端渲染中的参数// getInitialPropsCtx: {},// manifest,正常情况下不需要});res.send(html);}else {next();}})
render参数与返回值介绍
参数:
{// 渲染页面路由,支持 `base` 和带 query 的路由,通过 umi 配置path: string;// 可选,初始化数据,传透传到 getInitialProps 方法的参数中initialData?: object;// 可选,html 模板,这里可自定义模板,默认是用 umi 内置的 htmlhtmlTemplate?: string;// 可选,页面内容挂载节点,与 htmlTemplate 配合使用,默认为 rootmountElementId?: string;// 上下文数据,可用来标记服务端渲染页面时的状态context?: object// ${protocol}://${host} 扩展 location 对象origin?: string;}
返回值:
{// html 内容,服务端渲染错误后,会返回原始 htmlhtml?: string | Stream;// 挂载节点中的渲染内容(ssr 渲染实际上只是渲染挂载节点中的内容),同时你也可以用该值来拼接自定义模板rootContainer: string | Stream;// 错误对象,服务端渲染错误后,值不为 nullerror?: Error;}
同样使用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的兼容都处理了,但是仍然存在大量第三方插件的兼容性问题。
 
