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_render
cd 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 setup
app.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/ssr
const { html, error, rootContainer } = await render({
// 有需要可带上 query
path: 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 内置的 html
htmlTemplate?: string;
// 可选,页面内容挂载节点,与 htmlTemplate 配合使用,默认为 root
mountElementId?: string;
// 上下文数据,可用来标记服务端渲染页面时的状态
context?: object
// ${protocol}://${host} 扩展 location 对象
origin?: string;
}
返回值:
{
// html 内容,服务端渲染错误后,会返回原始 html
html?: string | Stream;
// 挂载节点中的渲染内容(ssr 渲染实际上只是渲染挂载节点中的内容),同时你也可以用该值来拼接自定义模板
rootContainer: string | Stream;
// 错误对象,服务端渲染错误后,值不为 null
error?: 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的兼容都处理了,但是仍然存在大量第三方插件的兼容性问题。