RSC 与 SSR 的关系与选型
SSR 是关于「什么时候渲染」,RSC 是关于「组件在哪里执行」——把这两件事分开,选型题就变清楚了。
一、渲染时机与执行位置
这是两组正交的概念,混在一起是绝大多数误解的源头:SSR / CSR 说的是「渲染发生在什么时候」(服务端出 HTML 还是浏览器出 DOM),RSC / 普通组件说的是「这是什么类型的组件」(代码是否下发)。所以存在四种组合:RSC + SSR(服务端渲染出首屏 HTML,组件代码不下发)、RSC + 纯客户端(SPA 也可以在运行时消费 RSC payload)、普通组件 + SSR、普通组件 + CSR。
| 组合 | 渲染时机 | 组件代码是否下发 | 典型产物 |
|---|---|---|---|
| RSC + SSR | 服务端出 HTML | 不下发 | 内容站首屏,零 JS |
| RSC + 纯客户端 | 客户端消费 payload | 仅交互部分下发 | SPA 内嵌服务端取数 |
| 普通组件 + SSR | 服务端出 HTML | 全量下发再水合 | 传统 SSR 应用 |
| 普通组件 + CSR | 浏览器出 DOM | 全量下发 | 标准 SPA |
RSC 本身不产生 HTML——它产出的是一份描述组件树的序列化 payload(Flight),要让浏览器拿到可显示的 HTML,还得叠一层 SSR 或 SSG。所以「用了 RSC 就一定有 SSR」是错的。
二、四种组合各自的效果
四种组合各自成立:RSC + SSG 是内容站最优解(静态内容 + 零 JS);RSC + SSR 是个性化首屏、按需流式输出;普通组件 + SSR 是没上 RSC 的传统 SSR,完全有效;RSC + 纯客户端也成立——一个 SPA 也能在运行时消费 RSC payload,组件代码不下发,只是少了首屏 HTML 的优势。
// 服务端组件是默认(无指令);用 'use client' 划出边界,其下整棵子树成为客户端组件
// Page.jsx(Server Component)
import Modal from './Modal' // Client Component
import Cart from './Cart' // Server Component,仍不下发代码
export default function Page() {
return (
<Modal>
<Cart /> {/* 作为 children 传入,仍是 Server Component */}
</Modal>
)
}
// 即使在纯客户端应用里,也能用 RSC 运行时消费服务端产出的 payload
// 客户端拿到的是「渲染结果的描述」,而非组件源码——这正是 RSC 不依赖 SSR 的体现
import { createFromFetch } from 'react-server-dom-client'
const data = createFromFetch(fetch('/rsc?route=home'))
关键点:RSC 可以在纯客户端应用里使用,两者没有依赖;决定「要不要首屏 HTML」的是 SSR/SSG,不是 RSC。RSC 单独存在时,浏览器拿到的是组件树的序列化描述,要配合一个能消费它的客户端运行时才有意义;在纯静态导出场景里它退化为「构建期算好、产物是 HTML」,和 SSG 没有本质区别。
三、按内容形态选
决策顺序应该是:先问这个页面要不要 SEO 与首屏速度(决定 SSR/SSG),再问它的交互密度与依赖体积(决定 RSC)。内容型页面两者都要,用 RSC 打底、关键交互组件标 use client;中后台交互密集、无 SEO 需求,两个都不用,纯客户端更省心;营销页要 SEO 但内容静态,SSG 就够了,不必上 RSC。
内容型页面:RSC + SSR/SSG(首屏 + 减 bundle)
中后台系统:普通组件 + CSR(无 SEO,交互密集)
营销/文档页:SSG(内容静态,SEO 即可)
迁移成本也要算进去:状态管理要按「服务端数据」与「客户端交互状态」重新切分,第三方库要检查是否有 use client 兼容问题——不是所有库都能在服务端组件里直接用。决策顺序不能反,先定渲染时机再定组件类型。另外,RSC 带来的「直连数据源」是双刃剑:省了 API 层,但也把数据访问逻辑带进了组件树,权限与缓存要更上心,不能因为「能直接查库」就到处查。
判断一个页面到底是服务端渲染还是纯客户端渲染,关掉 JS 拉一次源码最直观:
# 返回里有真实内容 = 服务端产出了 HTML;只有一个空壳 div = 纯客户端渲染
curl -s https://example.com/product/1 | head -c 600
# 再看首屏 HTML 体积,体积过大本身就是 SSR 的代价之一
curl -s https://example.com/product/1 | wc -c
四、上 SSR 反而更差的时候
逐条说:纯内网后台、登录后才能看的系统,SEO 无收益,白付服务端渲染与流式成本;强实时交互应用(重度拖拽编辑、在线白板),hydration 成本大于收益,纯客户端反而更顺;离线优先的应用,首屏 HTML 本就来自缓存,SSR 帮不上忙。不上 SSR 不等于落后,是「这笔服务端开销换不来对应收益」。同理,RSC 也不该「为了用而用」——它解决的是 bundle 体积与直连数据源,中后台如果已经纯客户端且交互密集,引入 RSC 只增加边界与序列化成本。
两者确实是正交的,一个最小对照就能说明:
// ① RSC + SSR:服务端组件产出 HTML,客户端 JS 很少
export default async function Page() {
const list = await db.query('select * from posts')
return <List items={list} /> // 无 'use client',List 不进客户端产物
}
// ② RSC 但不 SSR:纯客户端应用也能用 RSC,产物小但首屏是空壳
// 差别只在「有没有服务端产出的首屏 HTML」,不在「组件跑在哪」
五、把两者当成二选一
用了 RSC 不代表一定有 SSR——RSC 可以在纯客户端应用里使用,两者没有依赖关系。
RSC 也不是为了让 SSR 变快。它的主要收益是减少下发到浏览器的代码量,速度和首屏是 SSR 的职责。
至于「所有项目都该迁 RSC」,更不成立:离线优先、重度拖拽编辑、频繁读浏览器状态的应用,迁过去只会增加心智负担。
先分清「什么时候渲染」和「什么类型的组件」,选型题就变成两道独立的是非题。
官方对几种渲染入口的定位写在 react.dev:Start a New React Project,选型时先看它怎么划分职责。