返回文章列表

SSR、CSR、流式渲染怎么选,真正先失控的往往不是性能,而是数据一致性

同一份数据取几次、在哪一层兜底、出错后由谁收口,比“首屏快了多少”更早决定前端方案会不会写乱

很多团队讨论 SSR、CSR、流式渲染,开口先问的都是性能。

首屏时间多少,LCP 能不能压下去,SEO 有没有帮助,接口能不能并行,Streaming 能不能更早出骨架。这些当然都重要,但真到了项目里,最先把页面搞乱的,经常不是指标,而是同一份数据到底被取了几次、在哪几层被缓存、失败以后谁来收口

我的判断是:前端渲染策略的关键,不在于 SSR、CSR、流式渲染谁更先进,而在于同一份页面数据要经历几次取数、几次水合和几套失败回退。页面一旦同时追求 SEO、交互速度和缓存命中,最先失控的通常不是性能数字,而是数据一致性、错误兜底和排障成本。

真正把页面写乱的,通常不是渲染方式本身

我见过不少页面,一开始只是一个很普通的内容详情页:

  • 列表页点进详情;
  • 首屏希望搜索引擎能抓,所以要 SSR;
  • 页面里又有收藏、评论、推荐位这些交互,所以客户端还得继续请求;
  • 为了让首屏更快,接口被拆成主数据和次要区块;
  • 为了提高命中率,服务端加了边缘缓存,客户端又加了一层请求缓存。

到这里为止,每个决定都能讲出理由。

问题出在这些决定叠在一起以后,页面里其实已经出现了至少四种“数据到达现场”的路径:

  1. 服务端首渲时拿到的那份数据;
  2. 浏览器水合时带下来的那份数据;
  3. 客户端挂载后重新发请求拿到的新数据;
  4. 用户交互后局部更新出来的派生数据。

如果这四条路径之间没有明确主次,页面就会开始出现一些很熟的怪问题:

  • HTML 里看到的是旧价格,水合后一闪变成新价格;
  • 首屏渲染显示“已收藏”,客户端接管后又变回未收藏;
  • 推荐区块晚到一步,把已经更新过的页面局部顶回旧值;
  • 服务端报错时走了一套兜底,客户端重试后又显示另一套兜底。

这些问题都不是“SSR 不够快”或者“CSR 太慢”,而是同一份页面状态被多次生成、多次覆盖,但没人定义谁说了算

SSR 解决的是首屏可见性,不自动解决数据真相源头

很多人喜欢把 SSR 当成“先把正确页面吐出来”,但这句话只在一个很窄的前提下成立:服务端拿到的数据,就是这次用户真正应该看到的最终数据。

现实里这个前提经常不成立。

比如详情页首屏的商品信息是服务端渲出来的,但下面这些信息往往不是:

  • 用户是否点过收藏;
  • 当前实验桶命中了哪套推荐;
  • 地理位置相关库存;
  • 登录态相关价格或权益;
  • 页面挂载后刚刚完成的异步更新。

这时候 SSR 只能保证“先吐出一版可展示结果”,不能保证这版结果就是整页最终真相。

一旦团队还要求“服务端先出 HTML,客户端接手后再补个性化”,系统就会自然分裂成两套判断:

  • 服务端判断这页应该先长什么样;
  • 客户端判断这页最终应该变成什么样。

如果两边取数条件、缓存时机、容错策略不完全一致,页面就一定会出现前后不一致。

所以 SSR 真正该问的不是“能不能更快出首屏”,而是:

  • 服务端这次输出的数据能保鲜多久;
  • 哪些字段允许水合后被覆盖;
  • 哪些字段必须以客户端最新结果为准;
  • 服务端失败和客户端失败,是不是同一套兜底语义。

这些问题不先讲清楚,SSR 只是在更早的时间点把某一版答案发给用户,不是在更高层面上解决一致性。

CSR 真正的问题,也不是慢,而是容易把刷新语义写散

CSR 常被批评首屏慢,这没错。但我更常见的问题是,CSR 项目很容易把“重新拉一次数据”写成一种不加约束的默认动作。

页面初始化拉一次; 切 tab 拉一次; 窗口聚焦再拉一次; 用户操作成功后顺手再拉一次; 请求库自动重试又补一次。

代码看起来都很自然,最后的效果却可能是:

  • 页面状态短时间内被多次覆盖;
  • 后返回的旧请求把先返回的新请求顶掉;
  • optimistic update 刚生效,又被全量 refetch 打回去;
  • 某个局部组件单独刷新,把页面级状态重新拼出另一版结果。

这类问题在 CSR 里特别多,因为客户端天然把“此刻再拉一次最新数据”看成低成本动作。但只要一个页面既有列表、又有摘要、又有交互反馈,refetch 不是刷新动作,而是一次重新裁决整页状态的行为

如果没有明确规则,CSR 最后就会变成:谁先拿到数据不重要,谁最后覆盖成功谁说了算。

这时候页面当然不稳定,和 CSR 本身关系没那么大,和你把写入路径放得太开关系更大。

流式渲染最容易被低估的成本,是“分批到达”带来的状态解释成本

流式渲染这两年很容易被讲成纯收益:

  • 骨架更早到;
  • 关键内容先出来;
  • 次要区块晚一点没关系;
  • 用户体感会更顺。

这些都是真的。但它有个代价很少在方案评审里被认真算进去:页面不再只有“到了”或“没到”两种状态,而是变成多个区块分批到达、分批出错、分批回退。

一旦页面被拆成主内容、评论、推荐、广告位、个性化浮层这些异步片段,你要处理的就不只是性能,而是下面这些工程问题:

  • 某个晚到区块依赖的主数据版本和首屏是不是同一版;
  • 主区块成功、次区块失败时,整页是否还能被视为成功;
  • 流里已经输出的内容能不能被后续片段推翻;
  • 用户在第一页片段到达后已经开始操作,后面补来的片段会不会覆盖交互结果。

这个成本不是抽象的。我见过一个页面把主信息 SSR 出来,把相关推荐和用户权益流式补上。结果线上最难排的 bug 不是慢,而是“某些用户看到的权益文案会在两秒内切换两次”。

最后查下来,不是单个接口挂了,而是:

  • 服务端首段用了公共缓存里的旧权益快照;
  • 后续流式片段用了带用户态的新接口;
  • 客户端水合后又根据本地登录态重算了一次展示字段。

三套来源都说得通,但它们拼在一起,页面就不像一个系统了。

真正该先设计的,是页面的数据主线

渲染策略讨论到最后,我越来越在意的不是“这个框架支不支持流式”,而是页面有没有一条清楚的数据主线。

所谓数据主线,不是把所有请求画在时序图上,而是把下面几件事先定死:

1. 哪一份数据是首屏基线

首屏 HTML 出来时,哪些字段已经可以被当成可展示真值,哪些只是占位结果,必须写清楚。

例如:

  • 文章正文可以以 SSR 结果为基线;
  • 用户点赞状态只能以客户端登录态请求为准;
  • 推荐区块从一开始就声明为“异步补全,不参与首屏真值”。

只要这层没定义,后面每多一轮请求,都是在和前一轮抢解释权。

2. 哪些字段允许覆盖,哪些字段只能增量补齐

很多页面写乱,是因为所有后到的数据都被默认成“最新、更值得相信”。其实不是。

有些字段适合覆盖,比如库存、价格、在线人数; 有些字段只能补齐,比如评论分页、推荐列表; 有些字段必须带版本判断后才能覆盖,比如用户刚操作过的收藏状态。

如果没有字段级别的覆盖规则,后来的请求越多,旧值顶新值的概率越大。

3. 服务端失败和客户端失败是不是一种语义

这点很容易被忽略。

有些系统里,服务端请求失败时会直接降级成默认模块;客户端失败时却显示独立报错重试按钮。用户看到的结果就是:

  • 刷新页面时,这块内容安静消失;
  • 页面挂载后,这块内容又突然弹出错误态。

这不是体验问题,而是系统对“失败”没有统一解释。

同一个区块,如果服务端和客户端失败语义不同,排障时就会很难回答:这到底算正常降级,还是异常显示?

一个常见误区:把“多拉一次最新数据”当成保险

很多团队遇到一致性问题,第一反应是“那客户端再补拉一次最新数据”。

这招短期看经常有效,因为它确实能把部分 SSR 旧数据刷新掉。但长期看,它很容易把问题从“首屏偶尔旧一点”升级成“整页到处在抢最终解释权”。

最典型的失败链路是这样的:

  1. 服务端先拿 A 版数据渲首屏;
  2. 浏览器挂载后客户端自动拉 B 版;
  3. 用户立刻点了收藏,本地 optimistic update 变成 C;
  4. B 版请求晚返回,把 C 顶回去;
  5. 收藏接口再返回,页面又变回 D。

从技术上说,每一步都“没错”。

但对用户来说,这个页面在三秒内换了四次答案。此时你很难再说问题核心是 SSR 还是 CSR,因为真正出问题的是:页面根本没有规定交互写入和全量刷新谁优先。

反例:不是所有页面都值得上 SSR 或流式

还有一个很现实的误区,是把渲染策略当成平台能力清单。

框架支持 SSR,就倾向于全站 SSR; 支持流式渲染,就开始把区块都拆出去; 担心 SEO,就默认详情页全走服务端; 担心慢,就再补一层客户端缓存。

最后不是页面变快,而是系统的解释成本直线上升。

有些页面其实根本不值得上复杂渲染策略:

  • 强登录态、强个性化、搜索引擎价值很低的后台页;
  • 数据实时性远高于静态首屏价值的交易页;
  • 首屏内容很少、但交互极多的操作页。

这类页面如果硬上 SSR,再叠客户端补拉和局部流式,收益往往不如老老实实把 CSR 下的数据写收敛。

相反,内容型详情页、公共落地页、结构稳定的资讯页,才更适合把 SSR 或流式收益吃满。前提仍然不是“能上”,而是页面的数据边界能不能被清楚拆开

怎么选,先看三件事

如果真要在 SSR、CSR、流式渲染之间做判断,我更建议先看这三件事,而不是先看框架宣传页。

第一,看首屏内容有没有稳定真值

如果首屏绝大部分内容在服务端就能拿到,而且用户态差异小,SSR 会更稳。

如果首屏高度依赖登录态、设备态、实时态,服务端拿到的只是一份很快过期的近似值,那 SSR 的收益就要打折,因为你很快还得让客户端重算一遍。

第二,看页面是否允许分批解释

如果页面天然可以分区块到达,比如正文先到、评论后到、推荐再后到,流式渲染更有价值。

如果页面里的几个区块共享大量状态,晚到一个就会推翻前面两个,那流式渲染带来的不只是性能优化,也是状态协调成本。

第三,看交互写入会不会和刷新路径打架

只要页面里存在收藏、购物车、表单、权限切换这类会即时改状态的动作,就要重点看交互写入路径和页面刷新路径是否会互相覆盖。

这件事没收住,选哪种渲染策略都只是换一种方式出错。

适用边界

这篇文章主要讨论的是:

  • 同时追求 SEO、首屏速度和个性化的 Web 页面;
  • 同一页面里既有服务端首渲、又有客户端补拉、还有局部异步区块的场景;
  • 使用现代前端框架时会自然碰到的水合、缓存、流式拼装问题。

如果你的页面非常简单,要么纯内容展示,要么纯后台操作,渲染策略的复杂度不会这么高。问题最容易爆发的,是那种“看起来只是一个详情页,实际上同时承载内容分发、商业转化和用户交互”的页面。

结尾

SSR、CSR、流式渲染都不是错,错的是把它们当成单纯的性能开关。

一旦页面同时有首屏诉求、缓存诉求和交互诉求,真正先失控的,往往不是指标,而是数据到底算哪一版、失败由谁兜底、交互写入和刷新路径谁优先。

所以前端渲染策略真正该先定的,不是“首屏快多少”,而是这页数据会经过几次生成、几次覆盖、几次失败回退,最后由哪一层收口

这件事不讲清楚,渲染方案越先进,页面越像是被很多套合理机制同时写出来的。