React Server Components 到底能不能用?我用一个中型项目测试后的血泪结论

🔑 关键词:React Server Components, React 19, RSC, SSR, 前端架构

📖 摘要:基于实际项目测试,对比RSC与传统SSR/CSR的性能和开发体验,给出真实建议。

我最近把一个中型电商项目从Next.js的Page Router升级到App Router,用上了React 19和RSC。原以为能大幅减少客户端JS,结果首屏JS只从168kb降到142kb,降幅不到16%,但TTI(Time to Interactive)反而从1.8s变成了2.1s。这让我非常困惑。后来我查了资料,发现RSC的流式渲染需要等待所有客户端组件的引用加载完成,才会触发空闲事件,所以首屏渲染虽然快了一点,但真正可交互反而慢了。

图片

接着我对比了同一个页面的三种渲染模式:传统SSR、纯CSR、以及RSC。数据很有意思。传统SSR的TTFB(首字节时间)是120ms,CSR的TTFB是30ms但首屏要等所有JS加载完才显示,RSC的TTFB是230ms。RSC的FCP确实比CSR快了近1.5秒,但LCP(最大内容绘制)却比SSR慢了200ms。为什么?因为RSC需要传输序列化后的组件树,这个序列化数据体积虽然不大,但需要前端解析并构建虚拟DOM,这个过程阻塞了关键渲染路径。

图片

在学习RSC的过程中,我踩了一堆坑。最典型的是"use client"指令带来的边界问题。有一次我写一个商品折扣组件,服务端直接查数据库得到一个BigInt类型的价格,然后我把它作为prop传给一个客户端组件,结果浏览器直接报错,说无法序列化BigInt。这个错误在文档里找半天才明白,因为RSC的server和client之间有序列化边界,所有跨边界的props必须是可序列化的。最后只能改成字符串。更头疼的是数据缓存逻辑。RSC自带一种基于时间戳的缓存,但缓存失效策略毫无规律。我用了stale-while-revalidate想实现数据实时刷新,结果页面上商品库存数据滞后了5分钟才更新,差点导致超卖。这就是生产事故。

图片

为了验证RSC是否值得,我用SvelteKit重写了同样的页面。结果包体积比RSC版本小了50%,而且我完全不用区分服务端组件和客户端组件。SvelteKit的SSR和hydrate机制虽然也有代码重复的问题,但至少不需要在每个文件里写指令。不过SvelteKit的生态确实比React差很多,我为了找一个轮播图组件,翻了好几个包才找到能用的。这让我意识到,RSC的问题不是技术本身,而是React团队把RSC定位成“新范式”,让很多人误以为所有场景都应该用。实际上RSC只适合那些只读的、数据不常变的展示型页面。我的购物车和用户中心改成纯CSR + SWR之后,体验反而更流畅了。

图片

最终我的独立观点是:RSC不应该成为默认选项,它只是一个条件性优化工具。只有当你的页面90%以上是静态内容、且服务端数据延迟在50ms以内时,RSC才能发挥最大价值。对于大多数中小团队,传统的SSR + 客户端水合 + 一个状态管理库(比如zustand)仍然是最稳妥的架构。别被React团队和Next.js的炒作带节奏。如果你真想试RSC,建议先选一个列表页跑通,用Lighthouse和WebPageTest测一下包体积和TTI,和原来的方案对比,再决定是否全面铺开。毕竟我们开发者的目标是提供流畅的用户体验,而不是追新框架。

图片

🏷️ 标签: