React Server Components 实战:什么时候该用,什么时候不该用
React Server Components(RSC)上线以来争议不断:有人觉得它是未来的方向,有人觉得它让架构变复杂。作为一个把博客从纯客户端改成 RSC 的实践者,我想聊聊真实的边界在哪里。
先说结论
RSC 适合:读取数据、渲染内容、SEO 敏感、几乎无交互的页面——比如文章列表、详情页、静态页。
RSC 不适合:需要频繁交互、依赖浏览器 API、状态管理复杂的组件——比如富文本编辑器、实时表单、拖拽面板。
一个真实的取舍案例
拿我改造博客的经历说。原来文章编辑器是全客户端组件(TipTap),文章列表是纯展示。改造成 RSC 后:
// 文章详情页 —— 服务端组件 ✅
export const dynamic = "force-dynamic";
export default async function PostPage({ params }) {
const { slug } = await params;
const post = await getPostBySlug(slug); // 直接读文件,无 API 层
return <ArticleContent post={post} />;
}
而编辑器里需要 useEditor、onChange 的部分,用 "use client" 单独隔离:
// 编辑器 —— 客户端组件 ✅
"use client";
export default function EditorForm() {
const editor = useEditor({ ... }); // 浏览器钩子,必须客户端
return <EditorContent editor={editor} />;
}
边界很清晰:谁需要浏览器能力,谁就标记 client。
三个关键认知
1. 序列化边界
服务端组件只能把"可序列化"的数据传给客户端组件。函数、类实例、Date 对象都不能直接传——传 params: Promise 这种就很麻烦。解法是服务端先处理好,只传纯数据:
// ❌ 不行:函数不能跨边界
<ClientComp onSave={savePost} />
// ✅ 可以:传纯数据,客户端自己处理
<ClientComp post={post} />
2. 动态渲染是显式选择
force-dynamic 之后,页面每次请求都重新执行——换来实时数据,代价是放弃静态缓存。对低流量个人站完全划算,对高流量站点要谨慎。
3. 客户端组件不是"二等公民"
RSC 的哲学是"默认服务端,按需客户端",但客户端组件依然是架构的一部分。一个常见的反模式是把整个页面标记为 client,就为了一个按钮——正确做法是把这个按钮拆出来。
容易踩的坑
| 坑 | 表现 | 解法 |
|---|---|---|
use client 传染 |
一个交互组件拖全家变 client | 交互能力下沉到叶子组件 |
| hydration 不一致 | 服务端和客户端渲染结果不同 | 检查是否依赖 window/localStorage |
| 参数类型 | params 是 Promise,直接访问报错 |
const { slug } = await params |
| 静态误判 | 页面被缓存,数据不更新 | 显式加 dynamic 或 revalidate |
小结
RSC 的正确打开方式不是"所有页面都用",而是按交互边界划分:
- 读数据的页面 → 服务端组件,白嫖渲染性能;
- 有交互的组件 → 客户端组件,隔离边界;
- 明确数据新鲜度需求 → 显式
dynamic。
架构没有银弹,但 RSC 给了我们一个更清晰的取舍工具。