SSR 中如何处理错误和降级?
简化版
SSR 错误处理要区分页面级错误、接口错误、组件错误和服务端运行时错误。关键页面不能因为非核心模块失败而整页白屏,应设置超时、兜底数据、错误边界、降级渲染、日志追踪和监控告警。
详细版
SSR 错误来源:
- 接口超时或失败。
- 组件渲染异常。
- 访问浏览器 API 报错。
- 数据序列化失败。
- 用户权限或登录态异常。
- 服务端资源不足。
处理方式:
- 关键接口失败返回错误页或状态码。
- 非关键模块失败展示占位。
- 请求设置超时。
- 服务端记录 requestId 和错误栈。
- 客户端水合错误上报。
- 提供降级到 CSR 的能力。
SSR 稳定性要求比普通 CSR 更高,因为错误可能直接影响 HTML 生成。
完整版教学
一、SSR 错误为什么更严重
CSR 页面即使某个组件错误,至少 HTML 壳可能已经返回。SSR 阶段如果渲染抛错,服务器可能无法生成页面,用户直接看到 500 或白屏。
因此 SSR 要把错误隔离和降级设计放在架构层。
二、接口错误处理
首屏关键数据失败时,可以返回错误页、404、重定向登录或展示明确错误状态。
非关键数据失败时,不应该拖垮整页。可以展示骨架、占位或客户端再重试。
接口必须设置超时,不能无限等待,否则 TTFB 会被拖死。
三、组件和环境错误
组件渲染时访问 window、document 是 SSR 常见错误。代码要区分服务端和客户端。
组件内部也要避免在渲染阶段做不稳定副作用。服务端渲染应该尽量是纯计算:输入数据,输出 HTML。
四、面试追问与工程落地
面试官可能问:“SSR 出错如何排查?”
要有 requestId 串联服务端日志、接口日志和前端上报。记录路由、用户状态、数据请求耗时、错误堆栈和构建版本。没有这些信息,线上 SSR 问题很难定位。
工程中建议建立错误分级:核心失败、模块失败、客户端水合失败分别处理。
五、错误分类决定 HTTP 与页面结果
| 错误类型 | HTTP/页面策略 | 是否告警 |
|---|---|---|
| 路由资源不存在 | 404 页面 | 低频统计,突增告警 |
| 未认证/无权限 | 302 登录或 401/403 | 视异常率 |
| 关键数据失败 | 5xx/错误页或安全降级 | 是 |
| 推荐模块失败 | 页面继续、模块占位 | 按模块 SLO |
| 客户端水合失败 | HTML 已返回,上报客户端 | 是 |
| 请求被取消 | 停止工作,不当系统错误 | 通常不告警 |
如果三个模块耗时分别为 100、200、2000ms,等待全部会让 TTFB 超过 2 秒。给非关键模块 300ms 预算后输出占位,关键主体仍可约 200–300ms 完成;降级必须基于业务重要性而不是一律吞错。
错误处理不是“catch 住”,而是保住正确状态码、隔离故障范围、给用户可恢复路径,并留下足够证据定位。
六、流式提交、日志和演练
响应头一旦提交,后续流式模块抛错通常不能把 200 改成 500。关键鉴权、路由存在性和必要数据应在首块发送前完成;流内错误通过边界占位、客户端恢复和日志处理。
requestId=abc123
route=/product/42 build=2026.07.28
phase=data-fetch dependency=inventory duration=302ms
error=Timeout fallback=client-retry
requestId 要贯穿 SSR 网关、后端 API 和客户端上报。日志避免记录完整 Cookie/token/个人数据;错误聚合按版本、路由、阶段和依赖分组,才能在一次发布后看出特定组件失败率上升。
定期故障演练:让推荐 API 超时、关键 API 500、序列化遇到循环对象、客户端水合报错、流发送一半断开,验证状态码、占位、重试、告警和进程稳定性。只测 happy path 无法证明 SSR 可用。
降级内容也必须正确:库存接口失败时不能默认显示“有货”,权限接口失败时不能默认放行。安全和交易相关数据应采用 fail closed,推荐内容等低风险模块才适合空占位。
错误页本身应保持依赖最少,避免它再次调用同一个故障服务而形成递归失败。
七、常见误区与追问
- 误区:SSR 最外层加一个 try/catch 就完成容错。 这无法区分 404、权限、关键依赖和非关键模块,也会丢失正确状态码。
- 误区:所有接口失败都应该返回整页 500。 非关键模块可局部降级,避免扩大故障面。
- 误区:流开始后仍能随时修改 HTTP 状态。 头提交后通常不能改,关键判断必须前置。
- 追问:为什么每个请求都需要 requestId? 它把浏览器、SSR 层和多个下游日志串成一条链路。
- 追问:降级到 CSR 是否总能救场? 客户端仍依赖同一故障 API 时无效,且要避免重复请求风暴。
- 追问:超时设置多长? 从页面总 SLO 反推,给关键和非关键依赖不同预算,并保留渲染与传输时间。
- 追问:SSR 异常会不会拖垮进程? 未捕获异常、内存泄漏和资源耗尽可能影响其他请求,需要进程级隔离与重启策略。
八、加强记忆
- 先分类:路由、身份、关键数据、非关键模块、水合与取消分别处理。
- 设预算:页面总 deadline 向下游分配,非关键超时就降级。
- 前置判断:流发送前确定状态码、鉴权和关键页面结果。
- 隔离范围:模块边界保整页,进程边界保其他请求。
- 串联证据:requestId、版本、路由、阶段、耗时和错误栈可观测。
- 主动演练:超时、500、序列化、水合和断流场景都要验证。