找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4553

积分

0

好友

593

主题
发表于 1 小时前 | 查看: 1| 回复: 0

TanStack 停用 RSC:科技概念插画

TanStack 官网一度是 React Server Components 的金字招牌。今年年初他们刚完成基于 RSC 的性能改造,7 月底 Tanner Linsley 就发了一篇博客,标题是《我们停用了 RSC》。同一个团队、同一条内容 pipeline,半年内得出两个相反的结论。背后的原因是什么?

主要是因为原本需要靠 RSC 解决的性能问题,已经被更彻底的方案化解了。

他们当初为什么会用 RSC?

tanstack.com 的问题很典型。内容页需要把所有 markdown 和语法高亮栈都塞给浏览器。当时一个文档页,总计传输约 1.1 MiB 脚本,其中 358 KiB 属于语法高亮,主要是 Shiki 的运行时、主题和语言分块。Markdown 解析也在客户端完成,用户打开一篇文档,要处理的东西实在太多。

将 RSC 引入页面改造后,收益非常明显。把 markdown 渲染和高亮挪到服务端处理,博客页和文档页的客户端 JS 各减少 153 KB,示例页减少 40 KB。生产页面的表现也随之改善:/blog/react-server-components 的 Lighthouse 从 52 涨到 74,Total Blocking Time 从 1200ms 降到 260ms,传输体积从 1101 KiB 压缩到 785 KiB。这些数字在当时被广泛称道,RSC 似乎成了解决内容站性能的标准答案。

服务器机柜与芯片连接概念图

还有需要去思考的问题

完成 RSC 改造后,很多人忽略了关键一点:渲染 markdown 和高亮代码为什么需要 358 KiB 这么大?RSC 只是把这笔渲染成本挡在浏览器外面,并不会让渲染器本身变小。如果依赖能做得足够小,小到可以直接交给浏览器处理,那 RSC 还值得用吗?

沿着这个方向,他们把原来的栈换成了两个轻量库,@tanstack/markdown@tanstack/highlight,只围绕 tanstack.com 必要的渲染契约来设计。结果渲染器的大小缩小到约 27 KiB(传输),比 RSC 版本多出大约 18~19 KiB。这个结果彻底改变了决策。RSC 原本是在用架构手段来掩盖依赖臃肿的问题,当依赖瘦身成功,架构的额外代价就全暴露出来了。

前后两轮数据,说明什么

拆掉 RSC 之后,他们又做了一次直接对比。旧版站点仍在线上,正好作为对照组。需要说明,这不是严格的实验室测试,新版还包含后续正常迭代的改动,不能把每个字节的差异都归因于 RSC。来看两个核心路由:/blog/react-server-components 传输体积从 1086 KiB 降到 889 KiB,TBT 从 139ms 降到 66ms;/router/latest/docs/overview 从 1017 KiB 降到 836 KiB,TBT 从 209ms 降到 115ms。Lighthouse 分数一升一平,分数本身不必过分计较,但字节数和主线程表现的走向是一致的:当前 SSR 版整体更小,TBT 更低。

载荷明细更有意思。文档体积从 99 KiB 降到 37 KiB,应用 JS 从 526 KiB 降到 349 KiB,渲染器反而从 9 KiB 涨到 27 KiB。整个站点多出来的,只有那十几 KiB 的渲染器,加上几 KiB 的 CSS。省掉的却是几十 KiB 的文档和一百多 KiB 的应用代码。

六页浏览,账算得更清楚

首屏对比其实是 SSR 最不利的比法,因为浏览器只在第一次加载时为渲染器付一次成本。之后的每一页,天平都会继续向 SSR 倾斜。RSC 版本中,每次导航、每次重新请求,服务端都要重新生成序列化后的组件树并发送给客户端,哪怕内容没变,这棵树也得重新传一遍。SSR 版本只付一次渲染器成本,之后服务端函数只返回变化的内容数据——文档请求就传 markdown,示例请求就传源码,客户端本地就能渲染。

实测下来,每条数据请求能省 1.5~5.6 KB。看起来不多,但 tanstack.com 的平均访问大约六页,渲染器成本只出现一次,而 payload 的节省每次都生效。RSC 把一个可复用的客户端依赖,变成了需要反复传输的序列化输出。依赖很大的时候,这笔账是划算的;依赖缩到几十 KiB 之后,付一次成本、之后只传数据差异的方案,显然更符合真实流量特征。

立方体与三角锥序列,暗示数据传输变化

代码形态的隐性成本

性能之外,还有一层更难量化的代价:内容管线从“内容”变成了“运行时边界”。在 RSC 版本里,markdown 先在 server‑only 文件里变成 JSX,JSX 再变成 Flight payload,路由拿到的只是一个叫 contentRsc 的 React 节点。改一行内容,可能要顺着代码判断它该碰运行时的哪一侧。换成组件,问题就变成它是不是跨了服务端边界,这类边界判断每次都得重新做一遍。

export async function renderMarkdownToRsc(content: string) {
  const { content: contentJsx, headings } = await renderMarkdownToJsx(content)
  const contentRsc = await renderServerComponent(
    React.createElement(React.Fragment, null, contentJsx),
  )

  return {
    contentRsc,
    headings,
  }
}

这段代码的问题也很明显:边界周围堆积起了不小的认知负担。维护这条 pipeline 的开发人员,必须时刻记住哪些文件是普通 React、哪些是 server‑only、哪些值还是原始内容、哪些已经是序列化结果,非常复杂。这次迁回 SSR 时,他们一个 commit 就删掉了 555 行 RSC 专属内容和 994 行旧渲染插件,重新回归到服务端函数返回内容数据、MarkdownContent 直接接收 markdown 进行渲染的简单路径上来。

数字5构建立体,象征代码精简

RSC 还能怎么用?

话说回来,TanStack 当初用 RSC 并不是瞎折腾,它确实解决了一个真实问题,收益也是实打实的。我也不想仅用两条路由就断言普通 SSR 在任何场景都优于 RSC,两条路由说明不了那么多。RSC 真正有价值的场景,是让昂贵的组件逻辑和依赖留在服务端,不进客户端 bundle。渲染器、解析器、高亮器这类大家伙,放在服务端很划算。但当渲染器缩到几十 KiB 之后,这套机制就有点杀鸡用牛刀了。

这也能解释一个现象:Next.js 把 RSC 做成了默认架构,于是“支不支持 RSC”成了框架的标配问题。很多人连自己要解决什么问题都没想清楚,就先问框架支不支持。其实,支撑 RSC 和强制使用 RSC,是两回事。RSC 应该是一个可选的原子能力,需要的时候再用,而不是每个新项目都默认开启。大多数应用今天可能用不上它,但开发者仍然希望有个选项放在那儿,总比没有强。

如果你正在评估 RSC,不妨先问自己一个问题:服务端边界到底在替什么兜底?如果答案是一个巨大的解析器或渲染器,RSC 的账还算得过来。如果答案只是“因为 Next.js 默认开着”,那这个架构很可能正在为一个并不存在的依赖问题付钱。先把依赖做小,让内容重新回归内容,或许才是大多数项目该走的路。

如果你也在思考架构选型与性能的平衡,不妨来 云栈社区 和更多开发者交流实践经验。




上一篇:多策略对冲基金进入“超级平台”时代:Millennium拟募资200亿美元
下一篇:Telegram被苹果短暂下架,创始人警告:黑产新手段威胁所有App
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-6 06:25 , Processed in 0.783706 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表