React Server Components(RSC)是 React 18 引入、在 Next.js App Router 中正式落地的服务端渲染范式。它让组件直接在服务端运行,把渲染结果以最小载荷流式传送到浏览器,从根本上削减客户端 JavaScript 体积,并让数据获取离数据库更近。本文带你从 CSR、SSR 一路梳理到 RSC,并用可运行的代码讲清它的工作机制与落地收益。
一、为什么需要 React Server Components
传统 CSR(Client-Side Rendering)把整页 JS 包下载到浏览器后再渲染,首屏要等”下载包 → 执行 → 请求数据 → 渲染”四步。SSR(Server-Side Rendering)把首屏 HTML 在服务端生成好,解决了首屏白屏,但交互仍需” hydrate(注水)”整棵组件树,客户端 JS 体积并没减少。RSC 的突破在于:组件可以只在服务端存在,永远不会进入浏览器包,因此既能拿到服务端资源(数据库、内部 API、密钥),又不必为它付出任何前端打包成本。
换句话说,RSC 把”组件”从纯前端概念扩展为”可在服务端运行的计算单元”,让渲染位置成为架构决策,而不是被构建工具一刀切。
二、CSR、SSR 与 RSC 的渲染模型对比
| 维度 | CSR | SSR | RSC |
|---|---|---|---|
| 组件运行位置 | 浏览器 | 服务端(首屏)+ 浏览器(hydrate) | 服务端(可零客户端 JS) |
| 首屏 HTML | 无(白屏) | 有 | 有(可流式) |
| 客户端 JS 体积 | 大 | 大(整树 hydrate) | 仅交互部分 |
| 能否直连数据库 | 否 | 首屏阶段可 | 可(推荐) |
| 典型场景 | 后台系统 | 内容站/电商 | 数据密集+重交互混合 |
可以看出,RSC 不是取代 SSR,而是与之叠加:Next.js 在 App Router 下,默认每个组件都是 Server Component,需要交互时再显式标注为 Client Component。
三、RSC 的核心工作机制
3.1 服务端组件与客户端组件的边界
在 App Router 中,app/ 目录下任意文件默认就是 Server Component:它可以是 async 函数,可以在其中 await 数据、读文件、查数据库。只有带 'use client' 指令的文件(及其子组件)才会被打包进浏览器,用于承载状态、事件与浏览器 API。
3.2 “use client” 指令
'use client' 是一个边界标记:它下方的组件树进入”客户端世界”。注意它标注在文件顶部,且一个客户端组件可以把 Server Component 作为 children 传入——这是 RSC 最常见的组合模式。
3.3 流式渲染与 Suspense
RSC 支持把渲染结果分块流式下发。配合 <Suspense>,慢的部分(如评论区)可以单独”挂起”,先让正文上屏,加载完再补齐,首屏感知更快。
四、用 Next.js App Router 写出第一个 RSC
下面这个页面没有任何 'use client',它就是一个 Server Component,直接在服务端查询数据并返回 HTML:
// app/posts/page.tsx —— 默认即 Server Component
import { db } from '@/lib/db';
export default async function PostsPage() {
// 在服务端直接读库,不经过浏览器、不经过额外接口
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
对比 CSR:这里没有 useEffect 里发请求、没有 loading 态、没有水合整棵树。数据获取与渲染同处一地,代码更短也更安全(密钥不进前端包)。
五、数据获取:在服务端组件里直连数据库
RSC 的杀手锏是”数据离源更近”。一个常见架构是:Server Component 直接调用数据访问层,把查好的数据作为 props 下发给客户端交互组件:
// app/post/[id]/page.tsx
import { getPost } from '@/lib/posts';
import { LikeButton } from './LikeButton'; // Client Component
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
{/* 服务端查好的数据,直接传给客户端按钮做初始值 */}
<LikeButton initial={post.likes} />
</article>
);
}
// app/post/[id]/LikeButton.tsx —— 唯一需要进浏览器的部分
'use client';
import { useState } from 'react';
export function LikeButton({ initial }: { initial: number }) {
const [count, setCount] = useState(initial);
return (
<button onClick={() => setCount(count + 1)}>
点赞 {count}
</button>
);
}
这正体现了 RSC 的”分而治之”:静态与数据展示留在服务端,只有真正需要响应点击的 LikeButton 才带 JS 进浏览器。
六、”use client” 的边界与陷阱
新手最容易踩的坑,是误以为”服务端组件不能出现在客户端组件里”。事实相反——你可以把 Server Component 作为 children 传给 Client Component,React 会在服务端先把它渲染成 HTML 再下发。但反过来,客户端组件不能直接 import 服务端组件当普通子节点(会破坏边界)。
// ✅ 正确:把 Server Component 以 children 形式传入
// ClientModal.tsx ('use client')
export function ClientModal({ children }: { children: React.ReactNode }) {
return <div className="modal">{children}</div>;
}
// page.tsx(Server Component)
<ClientModal>
<ServerExpensiveList /> {/* 仍在服务端渲染 */}
</ClientModal>
另一个陷阱:别在 Server Component 里写事件处理函数或 useState,这些属于客户端能力,需要下沉到有 'use client' 的组件。判断标准很简单——”这段代码要不要在浏览器里跑?”要,就归客户端。
七、性能收益:更少的 JS,更快的首屏
7.1 削减客户端包体积
把图表、Markdown 渲染、重型列表等”纯展示”组件留在服务端,能从首屏 JS 中移除大量依赖。对一个数据密集的后台页,仅此一项常能把前端包缩减 30%–60%。想系统量化收益,可参考我们之前的前端性能优化:从 Lighthouse 90+ 到首屏 1s中的度量方法。
7.2 流式 SSR 提升体感速度
// 用 Suspense 把慢区块隔离,先出正文
import { Suspense } from 'react';
import { Comments } from './Comments';
export default function Page() {
return (
<article>
<h1>正文</h1>
<Suspense fallback={<p>评论加载中…</p>}>
<Comments /> {/* 慢查询,独立流式返回 */}
</Suspense>
</article>
);
}
八、与现有生态的取舍
RSC 不否定客户端状态管理。客户端该管的(表单、动画、实时状态)依旧用 useState、Zustand 等;服务端负责”取数 + 首屏骨架”。二者是分层而非互斥。工程化层面,RSC 与前端构建工具演进:Webpack → Vite → Rspack并不冲突——构建工具优化的是客户端包的编译速度,RSC 优化的是渲染位置与分包边界。
若你在 Vue 技术栈,可对照Vue3 + TypeScript 工程化理解”同构渲染”的异同:Vue 的 SSR 与 RSC 思路相通,但 RSC 把”组件级服务端/客户端边界”做得更细。
九、常见误区与调试技巧
误区 1:所有组件都加 ‘use client’ 最省事。这会退回 CSR,失去 RSC 的体积优势。默认不加,只在需要时加。
误区 2:把 fetch 写在客户端更接近用户。RSC 在服务端 fetch 通常延迟更低(同机房访问数据库),还能用 cache 与 revalidate 做请求记忆与增量再生。
调试技巧:用 Next.js 的 React DevTools 或 next build 输出的”First Load JS”大小,观察某个页面客户端包是否因误加边界而膨胀。
十、总结与落地建议
React Server Components 不是又一个渲染库,而是把”组件运行在哪里”变成一等架构决策。落地时记住三条:默认 Server Component,按需才 ‘use client’;把取数尽量上提到服务端;用 Suspense 隔离慢区块做流式首屏。对于数据密集、既要 SEO 又要交互的内容型站点,RSC 几乎是当下最划算的性能杠杆。下一步可结合首屏优化清单做一轮前后包体积与 LCP 的对比实测,让收益可被度量。




