React Server Components(RSC)是 React 18+ 最具架构性的一次变革:它允许组件在服务端执行,直接访问数据库和文件系统,渲染结果以序列化格式发送到客户端,大幅减少 JavaScript Bundle 体积。Next.js 的 App Router、Remix、Gatsby 5 等框架都已经深度集成 RSC。本文从原理到落地,完整拆解 RSC 的执行模型和应用策略。
一、Server Components 核心机制
1.1 执行位置差异
| 维度 | Server Component | Client Component |
|---|---|---|
| 执行环境 | 服务端(Node.js / Edge) | 浏览器 |
| Bundle 体积 | 0(不发送到客户端) | 包含在 JS Bundle 中 |
| 直接访问数据库 | ✅ 可以 | ❌ 不行(需 API) |
| 直接访问文件系统 | ✅ 可以 | ❌ 不能 |
| 使用 React Hooks | ❌ 不能用 | ✅ 必须 |
| 使用浏览器 API | ❌ 不能用 | ✅ 可以 |
| 处理用户事件 | ❌ 不行 | ✅ 必须 |
| “use client” 声明 | ❌ 不需要 | ✅ 需要(如果使用 Hook/事件) |
1.2 RSC Payload 传输格式
服务端渲染 RSC 组件
↓
生成 RSC Payload(特殊的序列化格式,不是 HTML)
↓
通过 HTTP 响应流传输给浏览器
↓
React 客户端运行时在浏览器中解析 RSC Payload
↓
解析为虚拟 DOM,与 Client Components 混合渲染
↓
hydrating,生成最终 DOM
RSC Payload 包含:
- 服务端组件的渲染结果(HTML 片段描述)
- Client Component 的引用(而非代码本身)
- 传递给 Client Components 的 props
- 内联的静态数据
1.3 RSC 的价值
- 零 Bundle 大小:服务端组件的代码不进入客户端 JS
- 直接访问后端资源:数据库查询、文件系统、内部 API
- 自动代码分割:Server/Client 边界即天然分割点
- 减少 API 往返:服务端直接获取数据,不需要客户端再请求
二、Server/Client 组件边界
2.1 默认规则
在 Next.js App Router 中:
- 默认所有组件都是 Server Components
- 只有在文件顶部声明
"use client"的才是 Client Components
2.2 选择决策
这个组件需要以下能力吗?
├── 使用 Hooks(useState/useEffect) → Client Component ✓
├── 处理用户事件(onClick/onChange) → Client Component ✓
├── 使用浏览器 API(window/document/localStorage) → Client Component ✓
├── 第三方库需要客户端运行(Chart.js/D3) → Client Component ✓
└── 都不能?→ Server Component ✓(默认)
└── 需要访问数据库/文件系统?→ 在 Server Component 中直接做 ✓
2.3 组件树中的交叉引用
Server Component (Dashboard.tsx)
└── Client Component (UserCard.tsx)
└── Server Component (UserStats.tsx) ❌ 不行!
Server Component (Dashboard.tsx)
└── Client Component (UserCard.tsx) ✓
└── Client Component (UserActions.tsx) ✓
Server Component (Dashboard.tsx)
└── Server Component (UserStats.tsx) ✓
└── Client Component (InteractiveChart.tsx) ✓
Client Component (UserCard.tsx)
└── Server Component (???).tsx ❌ 不能 import Server Component
核心规则:Client Component 不能 import Server Component,但 Server Component 可以 import Client Component 并通过 props 传递数据。
2.4 实际代码示例
// app/dashboard/page.tsx — Server Component(默认,不需要 "use server")
import { prisma } from '@/lib/prisma';
import { UserCard } from './UserCard'; // Client Component
export default async function DashboardPage() {
// 直接访问数据库
const user = await prisma.user.findUnique({
where: { id: '123' },
include: { posts: { take: 5 } },
});
return (
<main>
<h1>Dashboard</h1>
{/* 传递数据给 Client Component */}
<UserCard user={user} />
</main>
);
}
// app/dashboard/UserCard.tsx — Client Component(需要交互)
'use client';
import { useState } from 'react';
export function UserCard({ user }) {
const [showDetails, setShowDetails] = useState(false);
return (
<div>
<h2>{user.name}</h2>
<button onClick={() => setShowDetails(!showDetails)}>
{showDetails ? 'Hide' : 'Show'} Details
</button>
{showDetails && <p>{user.email}</p>}
</div>
);
}
三、数据获取与流式传输
3.1 Server Component 中的数据获取
// Server Component 中直接 await 异步数据
import { Suspense } from 'react';
import { PostList } from './PostList';
import { Comments } from './Comments';
export default async function PostPage({ params }: { params: { id: string } }) {
// 主数据可以直接 await(阻塞渲染直到数据就绪)
const post = await fetch(`https://api.example.com/posts/${params.id}`).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
{/* 次要数据用 Suspense 包裹,实现流式传输 */}
<Suspense fallback={<div>Loading comments...</div>}>
<Comments postId={params.id} />
</Suspense>
</article>
);
}
// Comments.tsx — 可以是另一个 Server Component
async function Comments({ postId }: { postId: string }) {
const comments = await fetch(`https://api.example.com/posts/${postId}/comments`, {
next: { revalidate: 60 }, // ISR
}).then(r => r.json());
return (
<ul>
{comments.map(c => <li key={c.id}>{c.text}</li>)}
</ul>
);
}
3.2 Streaming 工作原理
浏览器请求 /posts/123
↓
服务端立即开始响应(HTTP 200 + Transfer-Encoding: chunked)
↓
先发送:HTML 骨架 + Post 标题内容(立即可见)
↓
后台继续获取 Comments 数据
↓
数据就绪后,通过 chunk 发送 Comments HTML
↓
React 客户端把 Comments 插入到 Suspense fallback 的位置
用户感知:页面瞬间加载骨架,内容逐步填充(不是白屏等待全部数据)。
3.3 并行数据获取
// ❌ 串行获取(慢)
const user = await fetchUser();
const posts = await fetchPosts(user.id); // 等待 user 返回
const stats = await fetchStats(user.id); // 等待 posts 返回
// ✅ 并行获取(快)
const [user, globalStats] = await Promise.all([
fetchUser(),
fetchGlobalStats(),
]);
// 依赖 user.id 的再并行
const [posts, stats] = await Promise.all([
fetchPosts(user.id),
fetchStats(user.id),
]);
四、Server Actions(“use server”)
在 React 19 / Next.js 中,可以在 Server Component 中定义 Server Actions:
// app/actions.ts
'use server'; // 标记文件中所有导出为 Server Actions
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await prisma.post.create({
data: { title, content },
});
revalidatePath('/posts'); // 让 ISR 页面重新生成
}
export async function deletePost(id: string) {
await prisma.post.delete({ where: { id } });
revalidatePath('/posts');
}
// app/posts/page.tsx — Server Component
import { createPost } from './actions';
export default async function PostsPage() {
const posts = await prisma.post.findMany();
return (
<div>
<form action={createPost}>
<input name="title" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
</form>
<ul>
{posts.map(post => (
<li key={post.id}>
{post.title}
<DeleteButton id={post.id} />
</li>
))}
</ul>
</div>
);
}
// app/posts/DeleteButton.tsx — Client Component
'use client';
import { deletePost } from './actions';
export function DeleteButton({ id }: { id: string }) {
return (
<button onClick={async () => {
if (confirm('Delete?')) await deletePost(id);
}}>
Delete
</button>
);
}
Server Actions 的优势:
- 不需要单独的 API Route 文件
- 直接在表单
action属性上绑定 - 自动处理 CSRF 防护
- 可以调用
revalidatePath刷新缓存
五、性能对比
5.1 传统 CSR vs SSR vs RSC
| 指标 | CSR(React SPA) | SSR(Next.js Pages) | RSC(Next.js App Router) |
|---|---|---|---|
| 首屏(TTFB) | 快(CDN) | 慢(服务端渲染) | 快(Streaming) |
| 首次内容绘制(FCP) | 慢(JS 下载+执行) | 快(HTML 直出) | 快(骨架流式输出) |
| 可交互时间(TTI) | 慢(hydration) | 较慢(hydration) | 快(选择性 hydration) |
| JS Bundle | 完整 React + 应用代码 | 完整 React + 应用代码 | 仅 Client Components |
| 数据获取往返 | 客户端额外请求 | 服务端获取 | 服务端直接获取 |
| SEO | ❌ 需要 SSR 补充 | ✅ 好 | ✅ 好 |
5.2 RSC Bundle 体积差异
页面:电商商品详情页
传统 SSR Bundle:
├── ProductDetails.tsx (10KB)
├── ProductSpecs.tsx (5KB)
├── Reviews.tsx (8KB)
├── RelatedProducts.tsx (12KB)
└── 交互逻辑 (20KB)
= 55KB 客户端 JS
RSC Bundle(全是 Server Components):
├── ProductDetails.tsx (0KB,服务端渲染)
├── ProductSpecs.tsx (0KB,服务端渲染)
├── Reviews.tsx (0KB,服务端渲染)
├── RelatedProducts.tsx (0KB,服务端渲染)
└── 仅 AddToCartButton.tsx (3KB,Client Component)
= 3KB 客户端 JS(+ RSC Payload 流式传输)
常见减幅:RSC 架构通常将客户端 JS 减少 30-70%。
六、常见陷阱与最佳实践
陷阱 1:在 Server Component 中使用 Hooks
// ❌ 错误
async function Page() {
const [state, setState] = useState(0); // Server Component 不能用 Hook!
return <div>{state}</div>;
}
// ✅ 如果需要状态,拆分为 Client Component
'use client';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
陷阱 2:过度使用 Client Component
// ❌ 不需要 "use client" 的组件
'use client'; // 没必要!
function StaticCard({ title, description }) {
return (
<div>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}
// ✅ 删除 "use client",让它成为 Server Component
function StaticCard({ title, description }) { ... }
陷阱 3:在 Client Component 中 import Server Component
// ❌ Client Component 不能直接 import Server Component
'use client';
import { ServerCard } from './ServerCard'; // 错误!
// ✅ 方案:通过 props 传递 Server Component 的渲染结果
function ClientWrapper({ children }) {
return <div className="wrapper">{children}</div>;
}
// page.tsx(Server Component)
import ClientWrapper from './ClientWrapper';
import ServerCard from './ServerCard';
export default function Page() {
return (
<ClientWrapper>
<ServerCard /> {/* ✅ 在 Server Component 中渲染 */}
</ClientWrapper>
);
}
常见问题(FAQ)
Server Components 和 SSR 有什么区别?
| SSR | RSC | |
|---|---|---|
| 渲染时机 | 每次请求服务端渲染 HTML | 服务端渲染,但输出 RSC Payload |
| Hydration | 需要全量 hydration | 选择性 hydration(Client Components 才需要) |
| Bundle 体积 | 不变(全部代码到客户端) | 大幅减少(仅 Client Components) |
| 数据获取 | getServerSideProps | 直接 await 在组件中 |
SSR 是"服务端生成 HTML",RSC 是"服务端运行组件逻辑并传输序列化结果"。
React 19 的 RSC 稳定了吗?
React 19 已经发布了稳定的 Server Components API。Next.js App Router 从 13 版开始深度集成 RSC,到 14/15 版已经非常成熟。生产环境使用无问题。
所有框架都支持 RSC 吗?
目前深度支持 RSC 的框架:
- Next.js(App Router)— 最成熟
- Remix — 部分支持(React Router v7+)
- Gatsby — 5.x 支持
- TanStack Start — 新兴框架,基于 Solid/Vue 的类似概念
React 本身(不用框架)不支持 RSC,需要框架提供服务端运行时。
RSC 对 SEO 有帮助吗?
有。RSC 在服务端渲染 HTML 内容(Streaming),搜索引擎爬虫可以抓取。配合 Next.js 的 generateMetadata 和 generateStaticParams,SEO 效果与传统 SSR 一致但性能更好。
相关阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。