[TOC]
# SSR 简介
![](https://pic2.zhimg.com/80/v2-55b46047b7f4cdc7dd2af68f63919a41_1440w.jpg)
> SSR(Server-Side Rendering)并不是什么新奇的概念,前后端分层之前很长的一段时间里都是以服务端渲染为主(JSP、PHP),在服务端生成完整的 HTML 页面
摘自[前端渲染模式的探索](https://link.zhihu.com/?target=http%3A//www.ayqy.net/blog/csr-vs-ssr-vs-prerendering-vs-hydration/%23articleHeader3)
之所以要**在服务端完成组件渲染工作**,是因为有性能与可访问性两大优势
<br>
<br>
# 2大优势
## 性能
与 CSR(Client-side rendering)模式相比,SSR 的性能优势体现在 2 方面:
* 网络链路
* 省去了客户端二次请求数据的网络传输开销
* 服务端的网络环境要优于客户端,内部服务器之间通信路径也更短
* 内容呈现
* 首屏加载时间(FCP)更快
* 浏览器内容解析优化机制能够发挥作用
<br>
网络链路上,由服务端发出接口请求,将返回数据随 HTML 响应内容一次性传递到客户端,比 CSR 二次请求更快。并且服务端网络传输速度更快(可以有更大带宽)、通信路径更短(可以同机房部署)、通信效率也更高(可以走 RPC)
内容呈现方面,CSR 的 HTML 大多是个空壳儿:
~~~text
<!DOCTYPE html>
<html>
<head>
<title>My Awesome Web App</title>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
~~~
<br>
客户端拿到这种 HTML 只能立即渲染出一页空白,二次请求的数据回来之后才能呈现出有意义的内容,而 SSR 返回的 HTML 是有内容(数据)的,客户端能够立刻渲染出有意义的首屏内容(First Contentful Paint)。同时,静态的 HTML 文档让流式文档解析(streaming document parsing)等浏览器优化机制也能发挥其作用
<br>
**关键区别是 SSR 不依赖客户端环境,包括网络环境和设备性能**,即使用户的网络情况很糟(弱网)、设备性能很差(廉价、老旧设备),服务端渲染同样能够保障与最优用户环境(Wi-Fi 网络、高端设备)下相近的内容加载体验
<br>
## 可访问性
可访问性(accessibility)从两方面理解:
* 对人:古老、特殊的用户设备,比如禁用了 JavaScript
* 对机器人:爬虫程序等,典型的,搜索引擎爬虫
<br>
前者一般不必太过在意,后者要关注两大“客户”:
* 搜索引擎:SEO
* 社交媒体:抓取页面内容展示缩略信息(比如 Twitter 卡片等)
<br>
对 PC 站点而言,保证搜索引擎能够正确索引、准确理解页面内容,有重要的商业价值(搜索结果靠前,曝光量更大)。**移动端虽不必考虑搜索引擎爬取,但也有类似的社交分享需求,社交媒体会抓取目标页面中的图片等作为缩略信息**
P.S.诚然,有些搜索引擎能够正确爬取重 CSR 的 SPA,但不是全部,并且一大批社交媒体大都只从响应 HTML 中提取部分内容作为缩略信息,**动态渲染 HTML(部分)内容的需求真切存在**
虽具有这些优势,但 SSR 却远不如 CSR 应用广泛,是因为 SSR 面临着 6 大难题
<br>
<br>
# 6 个难题
## 如何利用存量 CSR 代码实现同构
**为了降级、复用、降低迁移成本等目的,通常会采用一套 JavaScript 代码跨客户端、服务端运行的同构方式来实现 SSR**,然而,要让现有的 CSR 代码在服务端跑起来,先要解决诸多问题,例如:
* 客户端依赖:分为 API 依赖和数据依赖两种,比如`window/document`之类的 JS API、设备相关数据信息(屏幕宽高、字体大小等)
* 生命周期差异:例如 React 中,`componentDidMount`在服务端不执行
* 异步操作不执行:服务端组件渲染过程是同步的,`setTimeout`、`Promise`之类的都等不了
* 依赖库的适配:React、Redux、Dva 等等,甚至还有**第三方库等不确定能否跑在 universal 环境,是否需要跨环境共享状态**,以状态管理层为例,SSR 要求其 store 必须是可序列化的
* 两边共享状态:每一份需要共享的状态都要考虑(服务端)如何传递、(客户端)如何接收
<br>
## 服务的稳定性和性能要求
与客户端程序相比,服务端程序对稳定性和性能的要求严苛得多,例如:
* 稳定性:异常崩溃、死循环
* 性能:内存/CPU 资源占用、响应速度(网络传输距离等都要考虑在内)
因此面临**后端专业性问题**,Demo 级的 SSR 可能并不难,但高可用的 SSR 服务却绝非易事,如何应对大流量/高并发,如何识别故障,如何降级/快速恢复,哪些环节需要加缓存,缓存如何更新……
<br>
## 配套设施的建设
SSR 最核心的部分是渲染服务,但除此之外还要考虑:
* 本地开发套件(校验 + 构建 + 预览/HMR + 调试)
* 发布流程(版本管理)
一整套的工程设施,在 SSR 模式下都需要重新考虑
<br>
## 钱的问题
引入 SSR 渲染服务,实际上实在网络结构上加了一层节点,而**大流量所过之处,每一层都是钱**:
> Most importantly, SSR React apps cost a lot more in terms of resources since you need to keep a Node server up and running.
将组件渲染逻辑从客户端改到服务器执行,计算资源的成本必须考虑在内
<br>
## hydration 的性能损耗
客户端接到 SSR 响应之后,为了支持(基于 JavaScript 的)交互功能,仍然需要创建出组件树,与 SSR 渲染的 HTML 关联起来,并绑定相关的 DOM 事件,让页面变得可交互,这个过程称为 hydration
<br>
**hydration 所需加载、执行的 JavaScript 代码不见得比 CSR 模式少多少**,这部分工作在客户端执行,受限于用户设备的性能,在较差的设备下可能会造成可感知的不可交互时间:
* CSR:可交互但是没有数据(还在异步请求数据,可能会持续很长)
* SSR:有数据但是不可交互(拉到 JS 后开始 hydrate 的过程,能看到内容但是不可交互,一般不会持续很长)
富交互的场景下,后者不一定比前者用户体验更好
<br>
## 数据请求
服务端同步渲染要求先发请求,拿到数据后才开始渲染组件,那么面临 3 个问题:
* 数据依赖要从业务组件中剥离出来
* 缺失客户端公参(包括 cookie 等客户端会默认带上的 header 信息)
* 两边数据协议不同:服务端可能有更高效的通信方式,比如 RPC
<br>
目前主流的 CSR 模式下,数据依赖与业务组件存在紧耦合,要由服务端发起的数据请求全都掺杂在组件生命周期函数中,**剥离数据依赖意味着需要同时改造 CSR 代码**。公参、数据协议等差异对代码复用、可维护性也提出了一些新的挑战
<br>
# 应用场景
无论首屏加载性能还是可访问性,都是对**内容密集型**页面才有意义,而对于交互密集型的页面,SSR 所能提前渲染的内容不多,对用户意义不大,SEO 的必要性也值得商榷。因此,**SSR 适用于偏静态的内容展示场景**,典型的,商品详情、攻略、文章等图文混排的场景
另一方面,不一定非要 100% SSR,渲染特定页面,甚至只渲染个页面框架也是不错的应用:
> "Application Shell" is an excellent concept. But sometimes, we might need to render a part of the page in the server. It could be the header with user info. In such cases, you need server-side rendering.
<br>
<br>
# 参考资料
[SSR 它到底香不香?细数 SSR 的利与弊](https://zhuanlan.zhihu.com/p/270149478)
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码