3.8 服务端渲染 SSR 与水合 SSR 让 Angular 在服务器上跑完首轮渲染,把带内容的 HTML 直接送达浏览器;水合让浏览器侧接管这批 DOM,附加事件与继续运行。主线在这里迎来最戏剧性的一幕:首轮变更检测发生在服务器上,渲染的不是真实浏览器 DOM,而是字符串;水合则是把静态产物重新接回检查管线的手术。 带着一个问题进入 带着一个问题进入:首屏白屏能不能既快又稳?本节用服务端渲染与水合给出工程答案: 启用 SSR 并描述服务端渲染-水合-客户端接管的三个阶段。 识别服务端与浏览器环境差异导致的报错(window、本地存储等)。 用转移状态避免接口被请求两次。 判断哪些页面值得 SSR、哪些不值得(成本核算)。
SSR 让 Angular 在服务器上跑完首轮渲染,把带内容的 HTML 直接送达浏览器;水合让浏览器侧接管这批 DOM,附加事件与继续运行。主线在这里迎来最戏剧性的一幕:首轮变更检测发生在服务器上,渲染的不是真实浏览器 DOM,而是字符串;水合则是把静态产物重新接回检查管线的手术。
带着一个问题进入:首屏白屏能不能既快又稳?本节用服务端渲染与水合给出工程答案:
# 创建时启用:ng new shop --ssr # 存量项目添加:ng add @nguniversal/express-engine(或新版内建方案)
启用后一次页面访问的三个阶段:
阶段一,服务端渲染。服务器上执行第 1 章的启动链路(无 Zone 信号的现代方案在服务端尤其顺滑——无需打补丁),组件树建好、首轮变更检测跑完,每个绑定的求值结果被序列化成 HTML 字符串。用户拿到的首屏是带完整内容的页面,搜索引擎抓到的也是它——SEO 与首屏指标的双收益。
阶段二,水合。浏览器下载应用代码后,不重建 DOM,而是对照服务端产出的 DOM 结构逐节点"认领":本组件的模板对应这批节点,绑定与事件监听重新接上。水合完成前页面可看不可点。
阶段三,客户端接管。此后一切回到本书主线:异步事件唤醒检查,界面继续演进。
三阶段里最值得盯的是水合:它不是免费的"接着用",而是一次全树核对。服务端 HTML 与客户端首次渲染若有出入(时间戳、随机数、按登录态分支的内容),水合就要做修补甚至局部重建,白屏时间悄悄回升。所以 SSR 项目的代码评审多了一条硬规矩:模板表达式不得依赖服务端与客户端不一致的值——需要这类值时推迟到接管后渲染,占位与骨架屏顶上。
服务器上没有 window、document、本地存储。直接使用的代码会炸掉整个服务端渲染。防御两式:
import { Component, Inject, PLATFORM_ID, inject } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; export class ChartPanelComponent { private platformId = inject(PLATFORM_ID); ngOnInit() { if (isPlatformBrowser(this.platformId)) { // 仅浏览器执行:图表库初始化、读取本地存储 this.initChart(); } // 服务端执行的部分:拉数据、渲染静态结构 } // 更精细的场景:等浏览器再渲染某区域(水合后再挂载) // 常见于依赖 window 尺寸的组件 }
原则:数据获取两侧都跑(或用状态转移只跑一次),环境专属 API 全部包平台判断。第三方库不支持 SSR 时,把整个组件标记为仅浏览器渲染,服务端用占位骨架。
朴素实现里,服务端拉了一次数据渲染 HTML,浏览器水合后又拉一次(组件初始化逻辑两侧都执行)——流量翻倍、界面可能闪变。状态转移的思路:服务端把拉到的数据随 HTML 一起下发,浏览器侧直接用它完成初始化,跳过第二次请求。
// 服务端:把关键状态写进页面(框架提供转移机制的注入点) // 客户端:初始状态从转移数据而来,而非重新请求 readonly orders = signal<Order[] | null>(null); constructor() { const transferred = this.readTransferredState<Order[]>('orders'); if (transferred) { this.orders.set(transferred); // 命中转移:不再请求 } else { // 纯客户端路径(如站内后续导航):正常拉取 this.api.list().subscribe(list => this.orders.set(list)); } }

背景:电商商品详情页自然流量依赖搜索,此前纯客户端渲染首屏指标差、爬虫抓不到价格。
操作:详情与首页启用 SSR,个人中心等登录页不启用;数据全部走状态转移;一个依赖 window 的评价图表组件标记为仅浏览器渲染;服务器按页面级缓存热门商品的首屏 HTML。
结果:首屏内容到达时间大幅缩短,搜索引擎收录正常化;服务器 CPU 占用上升但被页面缓存抵消大半。
解读:SSR 的成本核算是"服务器渲染开销 + 水合复杂度"对价"首屏速度 + SEO"。内容页收益为正;交互重的后台页面收益为负——这也回应了 2.6 节懒加载的分层:SSR 用于面向公网的内容层,后台管理保持纯客户端。
变式:静态化更进一步——构建期把热门页面预渲染为静态文件(无需运行时服务器),适合变化不频繁的营销页;配合 3.7 节 PWA,静态页加客户端水合,弱网体验也稳。
第 3 章完。下一章看生态:组件库、工具与版本演进。