为什么需要 Server Components
在理解 Server Components 之前,先看看它要解决什么问题。
传统 React 的困境
Bundle 体积膨胀:随着应用复杂度增加,客户端 JavaScript 越来越大。一个后台管理系统,光是日期选择器、富文本编辑器、图表库就能加起来好几百 KB。用户下载、解析、执行这些代码需要时间。
瀑布式数据获取:组件渲染后才知道需要什么数据,发起请求,等待响应,再渲染子组件,子组件又发起请求...这种串行的数据获取严重拖慢了首屏速度。
服务端/客户端割裂:传统 SSR 中,服务端渲染 HTML,客户端"水合"后接管。两边的逻辑是分离的,数据需要序列化传输,组件需要在两端都能运行。
Server Components 的目标是:让组件真正运行在服务端,只把渲染结果发送给客户端。
与传统 SSR 的区别
很多人初次接触 Server Components 会问:这不就是 SSR 吗?
实际上有本质区别:
| 特性 | 传统 SSR | Server Components |
|---|---|---|
| 运行位置 | 服务端渲染,客户端水合 | 只在服务端运行 |
| JavaScript | 发送到客户端 | 不发送到客户端 |
| 状态 | 客户端可交互 | 无客户端状态 |
| 更新方式 | 客户端重渲染 | 服务端重渲染 |
传统 SSR 的组件最终还是要在客户端运行,JavaScript 代码一个都不能少。Server Components 则真正把组件"留在"服务端。
工作原理拆解
RSC 协议
Server Components 的核心是一套新的传输协议,官方称之为 RSC 协议(React Server Components Protocol)。
当用户请求一个页面时:
- 服务端运行 Server Components,生成一种特殊的序列化格式
- 这个格式描述了组件树的结构和数据,但不包含组件的代码
- 客户端的 React 运行时解析这个格式,重建组件树
- Client Components 在客户端渲染并水合
关键点在于:Server Components 的代码永远不会发送到客户端。客户端收到的只是渲染结果的描述。
组件边界
Server Components 和 Client Components 如何协作?关键在于边界。
在组件树中,边界的划分遵循以下规则:
- Server Component 可以导入 Server Component
- Server Component 可以导入 Client Component(通过 children 或 props 传递)
- Client Component 不能导入 Server Component(但可以渲染作为 children 传入的 Server Component)
这个规则乍看复杂,背后的逻辑是:Client Component 需要在客户端运行,而 Server Component 的代码不存在于客户端。
数据流向
理解数据在两种组件间如何流动:
Server → Client:Server Component 可以把数据作为 props 传给 Client Component。这些数据会被序列化。
Client → Server:通过 Server Actions 实现。Client Component 调用一个标记为 'use server' 的函数,请求发送到服务端执行。
这种单向的数据流保持了清晰的架构,避免了复杂的同步问题。
实际应用中的思考
什么时候用 Server Component
适合的场景:
- 数据展示型组件(列表、详情、图表)
- 需要访问后端资源(数据库、文件系统)
- 使用大型依赖库(Markdown 解析、代码高亮)
- 静态内容(页头、页脚、导航)
不适合的场景:
- 需要用户交互(点击、输入、滚动)
- 使用浏览器 API(localStorage、window)
- 使用 React hooks(useState、useEffect)
- 需要实时更新的数据
什么时候用 Client Component
简单的判断标准:如果组件需要和用户交互,或者需要在客户端维护状态,用 Client Component。
常见的 Client Component 场景:
- 表单组件(输入、选择、上传)
- 交互动效(弹窗、抽屉、下拉菜单)
- 实时数据(聊天、通知、协作)
- 第三方库(地图、编辑器、播放器)
边界划分的艺术
边界划分直接影响应用的性能和开发体验。几个原则:
边界尽可能下沉:把 'use client' 放在需要交互的最小组件上,而不是在页面顶层。这样能最大化 Server Component 的范围。
数据就近获取:哪个组件需要数据,就在哪个组件获取。利用 Server Component 的并行获取能力,避免数据在组件间层层传递。
状态局部化:不需要全局共享的状态,不要放到全局。用 Client Component 管理局部状态,用 Server Component 管理全局数据。
性能影响分析
Bundle 大小
Server Components 最直观的优势是减少客户端 JavaScript。
一个真实案例:某后台系统使用 Monaco Editor 做代码编辑,这个库压缩后约 2MB。传统方式下,用户打开任何页面都要加载这 2MB(即使不用编辑器的页面)。
改用 Server Components 后,编辑器相关的代码只在使用的页面加载,其他页面的 JavaScript 减少了 60%。
首屏速度
Server Components 对首屏有两个正向影响:
减少 JavaScript 执行:更少的代码意味着更短的解析和执行时间。在低端设备上效果尤其明显。
流式渲染:Server Components 支持流式传输,可以边生成边发送。用户不需要等待整个页面准备好才开始看到内容。
但也有负向因素:
服务端计算时间:组件在服务端渲染需要计算资源。如果服务端响应慢,会抵消其他优势。
网络延迟:每次导航都需要请求服务端,对于网络较差的用户可能不如纯 SPA 流畅。
交互响应
Server Components 不影响客户端交互响应。一旦 Client Component 水合完成,交互体验和纯客户端应用一样。
但要注意:如果某个交互触发了 Server Component 的重新渲染(比如 Server Action 更新数据),会有一次网络往返。对于需要即时反馈的交互,考虑使用乐观更新。
常见误区与澄清
误区一:Server Components 取代 SSR
不是取代,是补充。Server Components 是组件级别的概念,SSR 是页面级别的渲染策略。两者可以结合:页面首次加载用 SSR,其中的 Server Components 在服务端渲染但代码不发送到客户端。
误区二:所有组件都应该是 Server Component
不是。Server Components 适合数据获取和展示,Client Components 适合交互。强行把交互组件做成 Server Component,会导致架构别扭、性能反而下降。
误区三:Server Components 是 Next.js 特有的
Server Components 是 React 的特性,Next.js 是目前最成熟的实现。理论上其他框架也可以实现 Server Components,未来可能会有更多选择。
误区四:Server Components 不能有任何客户端代码
Server Components 本身不运行客户端代码,但可以渲染 Client Components。一个页面可以是 Server Components 和 Client Components 的组合。
最佳实践
组件组织策略
推荐的文件组织方式:
components/
├── server/ # 纯 Server Components
│ ├── DataList.tsx
│ └── UserProfile.tsx
├── client/ # 纯 Client Components(标记 'use client')
│ ├── SearchInput.tsx
│ └── Modal.tsx
└── shared/ # 可在两端使用的组件(无状态、无副作用)
├── Button.tsx
└── Card.tsx
这种组织方式让组件的运行环境一目了然,避免混淆。
数据获取模式
直接 await:最简单的方式,组件直接 await 数据
// Server Component
async function UserList() {
const users = await db.user.findMany()
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
并行获取:多个独立数据源并行请求
async function Dashboard() {
const [users, orders, stats] = await Promise.all([
getUsers(),
getOrders(),
getStats()
])
// ...
}
Suspense 边界:控制加载状态的粒度
function Page() {
return (
<div>
<Header /> {/* 立即渲染 */}
<Suspense fallback={<Skeleton />}>
<SlowComponent /> {/* 流式加载 */}
</Suspense>
</div>
)
}
状态管理策略
服务端状态:用 Server Components 直接获取和渲染,不需要客户端状态库。
客户端状态:局部状态用 useState,跨组件状态用 Context 或状态库,但范围限制在 Client Components 内。
混合状态:服务端提供初始数据,客户端维护后续更新。适合实时性要求不高但需要交互的场景。
调试与排错
常见错误
"You're importing a component that needs X":Client Component 用了只能在服务端用的 API(比如 fs)。检查组件边界划分。
水合不匹配:服务端和客户端渲染结果不一致。常见原因是使用了 Date.now()、Math.random() 等不确定的值。
意外的服务端日志:console.log 出现在服务端终端而不是浏览器控制台,说明组件意外地在服务端运行。
调试技巧
React DevTools:最新版 DevTools 可以识别 Server Components,显示不同的图标。
网络请求分析:观察 RSC 响应的格式,理解数据是如何传输的。
组件边界检查:在文件开头加 console.log('server') 或 console.log('client'),确认运行环境符合预期。
未来展望
Server Components 是 React 团队对"组件应该在哪里运行"这个问题的回答。它不是银弹,但代表了一种值得关注的方向:
计算下沉:把能在服务端做的事情放到服务端,减轻客户端负担。
边界流动:服务端和客户端的边界不再是固定的,而是可以根据组件特性灵活划分。
生态演进:随着更多库适配 Server Components,开发体验会越来越好。
对于开发者来说,现在是学习 Server Components 的好时机——概念已经成熟,Next.js 提供了生产级实现,但大部分项目还没有大规模采用。早一步理解,就能早一步受益。
总结
Server Components 的核心价值:
| 价值点 | 具体体现 |
|---|---|
| 减少 Bundle | 组件代码不发送到客户端 |
| 直接访问后端 | 无需 API 中间层 |
| 自动代码拆分 | 按组件边界天然拆分 |
| 流式渲染 | 渐进式内容展示 |
| 简化数据流 | 数据就近获取 |
Server Components 不是要取代 Client Components,而是提供了一种新的选择。理解两者的边界和协作方式,才能发挥架构的最大价值。
相关文章推荐:


