React Server Components (RSC) 是 React 自 Hooks 以来最重要的架构变革。Next.js 16 将 RSC 作为默认范式,彻底改变了我们构建 Web 应用的方式。
但很多开发者在实际落地时仍然困惑:什么该放 Server?什么该放 Client?数据怎么流转?性能到底提升了多少?
这篇文章用实战代码把这些问题讲透。
1. RSC 到底是什么?一张图讲清楚
传统 React 组件在浏览器端运行。RSC 让组件在服务端运行,生成的是序列化后的 React 元素树,而不是 HTML 字符串。
关键区别
| | Server Components | Client Components | |---|---|---| | 运行环境 | 服务端 (Node.js/Edge) | 浏览器 | | 能否访问数据库 | ✅ 直接访问 | ❌ 需要 API | | 能否使用 State/Effect | ❌ | ✅ | | 能否绑定事件 | ❌ | ✅ | | JS Bundle 体积 | 0 KB | 包含完整代码 | | 能否直接 import 服务端库 | ✅ | ❌ |
核心价值一句话:Server Components 的代码永远不发送到客户端,但渲染结果可以。
2. Server Components 的四大杀手级优势
2.1 零 JS Bundle 开销
这是最直接的好处。一个 500KB 的 Markdown 渲染库,如果只在 Server Component 中使用,对客户端的 JS Bundle 贡献是 0 字节。
// Server Component — 0 KB 发送到客户端
import matter from 'gray-matter'; // 服务端库
import { remark } from 'remark';
import html from 'remark-html';
// 不需要 'use client' 指令
export async function MarkdownRenderer({ content }: { content: string }) {
const { data, content: mdContent } = matter(content);
const processed = await remark().use(html).process(mdContent);
return (
<article>
<h1>{data.title}</h1>
<div dangerouslySetInnerHTML={{ __html: processed.toString() }} />
</article>
);
}
2.2 直接访问后端资源
Server Components 可以直接查询数据库、读取文件系统、调用内部 API,不需要额外的 API 路由层。
// Server Component — 直接查询数据库
import { db } from '@/lib/db';
import { users } from '@/lib/db/schema';
export async function UserList() {
const allUsers = await db.select().from(users).limit(10);
return (
<ul>
{allUsers.map(user => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
);
}
对比传统架构:Client 发 fetch → API Route 查 DB → 返回 JSON → Client 渲染。RSC 架构:直接查 DB → 渲染 → 发送结果。少一跳网络往返。
2.3 自动代码分割
'use client' 指令不仅标记了客户端组件,还隐式创建了一个代码分割点。Next.js 16 会自动将 Client Component 及其依赖打包成独立的 JS chunk,按需加载。
2.4 Streaming SSR 原生支持
Server Components 天然支持流式传输。组件渲染完成后立即发送,不等待所有组件就绪。
// app/blog/[id]/page.tsx
import { Suspense } from 'react';
export default function BlogPage({ params }: { params: { id: string } }) {
return (
<article>
<BlogContent id={params.id} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments id={params.id} />
</Suspense>
<Suspense fallback={<RelatedSkeleton />}>
<RelatedPosts id={params.id} />
</Suspense>
</article>
);
}
用户看到标题和正文的速度比等待所有组件就绪快 40%-60%。
3. Server/Client 组件拆分实战原则
这是 RSC 实践中最重要的决策。记住三条铁律:
铁律 1:默认 Server,只在必要时用 Client
默认写 Server Component
↓ 需要交互?
↓ 需要 State/Effect?
↓ 需要事件处理?
↓ 需要浏览器 API?
↓ 是 → 加 'use client'
铁律 2:Client 组件尽量靠叶子节点
不要让整个页面树变成 Client Component。把交互隔离到最小的叶子组件。
// ❌ 错误:整个页面都是 Client Component
'use client';
export default function DashboardPage() {
const [data, setData] = useState(null);
return <div>{/* 全部在客户端渲染 */}</div>;
}
// ✅ 正确:Server 组件包裹 Client 叶子组件
// page.tsx (Server Component)
import { db } from '@/lib/db';
import { LikeButton } from './LikeButton';
export default async function ArticlePage({ params }: { params: { id: string } }) {
const article = await db.article.findUnique({ where: { id: params.id } });
return (
<div>
<h1>{article.title}</h1>
<p>{article.content}</p>
<LikeButton articleId={article.id} initialLikes={article.likes} />
</div>
);
}
// LikeButton.tsx (Client Component)
'use client';
import { useState, useOptimistic } from 'react';
export function LikeButton({ articleId, initialLikes }: {
articleId: string;
initialLikes: number;
}) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
initialLikes,
(state) => state + 1
);
return (
<button
onClick={async () => {
addOptimisticLike(null);
await fetch(`/api/articles/${articleId}/like`, { method: 'POST' });
}}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg"
>
❤️ {optimisticLikes}
</button>
);
}
铁律 3:Server → Client 传递的 Props 必须可序列化
Server Component 向 Client Component 传递的数据必须能被 JSON.stringify。这意味着不能传递函数、类实例、Symbol、循环引用。
4. 四种数据流模式
模式 A:Server Component 直接获取数据(推荐)
async function getArticle(id: string) {
const res = await fetch(`https://api.example.com/articles/${id}`, {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function Page({ params }: { params: { id: string } }) {
const article = await getArticle(params.id);
return <ArticleView data={article} />;
}
Next.js 16 会自动去重同一渲染周期内的相同 fetch 请求。
模式 B:Server Actions 写操作
// app/actions/article.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createArticle(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.article.create({ data: { title, content } });
revalidatePath('/blog');
}
// app/blog/new/page.tsx (Server Component)
import { createArticle } from '@/app/actions/article';
export default function NewArticlePage() {
return (
<form action={createArticle}>
<input name="title" placeholder="文章标题" required />
<textarea name="content" placeholder="文章内容" required />
<button type="submit">发布</button>
</form>
);
}
模式 C:Server → Client 流式数据
// app/dashboard/page.tsx
export default async function DashboardPage() {
const [stats, recentPosts, topAuthors] = await Promise.all([
getStats(),
getRecentPosts(),
getTopAuthors(),
]);
return (
<div className="grid grid-cols-3 gap-6">
<StatsCard data={stats} />
<Suspense fallback={<Skeleton />}>
<RecentPosts data={recentPosts} />
</Suspense>
<TopAuthors data={topAuthors} />
</div>
);
}
模式 D:Client 组件驱动数据获取
'use client';
import useSWR from 'swr';
export function SearchResults({ query }: { query: string }) {
const { data, error, isLoading } = useSWR(
query ? `/api/search?q=${query}` : null,
fetcher
);
if (isLoading) return <Spinner />;
if (error) return <ErrorView />;
return <ResultList items={data.results} />;
}
5. PPR(部分预渲染)实战
Next.js 16 的 PPR 是 RSC 的终极形态。它让一个页面的静态部分在构建时预渲染(Static Shell),动态部分在请求时流式注入。
// next.config.ts
export default {
experimental: {
ppr: true, // 开启 PPR
},
};
// app/page.tsx
import { Suspense } from 'react';
export default function HomePage() {
return (
<div>
{/* Static Shell — 构建时生成,瞬间展示 */}
<header className="sticky top-0">
<Logo />
<Navigation />
</header>
{/* Dynamic Hole — 请求时流式填充 */}
<Suspense fallback={<TrendingSkeleton />}>
<TrendingArticles />
</Suspense>
<Suspense fallback={<PersonalizedSkeleton />}>
<PersonalizedRecommendations />
</Suspense>
</div>
);
}
PPR 的巧妙之处在于:用户在 CDN 边缘就能拿到完整的 Static Shell(< 50ms),Dynamic Holes 的内容在请求时异步流式填充。LCP 从 3.2s 降到 0.8s。
6. RSC 场景下的 AI 应用架构
AI 应用是 RSC 最理想的场景之一。
6.1 AI 推理放在 Server Component 中
// app/ai/summarize/page.tsx
export default async function SummarizePage({
searchParams,
}: {
searchParams: { url?: string };
}) {
if (!searchParams.url) {
return <UrlInput />;
}
const content = await fetch(searchParams.url).then(r => r.text());
const summary = await summarizeWithAI(content);
return (
<div>
<h2>AI 摘要</h2>
<p>{summary}</p>
</div>
);
}
优势:API Key 不会暴露到客户端;AI 推理的延迟被 Streaming 掩盖。
6.2 AI 流式输出 + RSC
// app/ai/chat/page.tsx
import { Suspense } from 'react';
import { ChatInput } from './ChatInput';
import { StreamingResponse } from './StreamingResponse';
export default function ChatPage() {
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto">
<Suspense fallback={<p>AI 正在思考...</p>}>
<StreamingResponse />
</Suspense>
</div>
<ChatInput />
</div>
);
}
7. 常见陷阱与避坑指南
陷阱 1:在 Server Component 中使用 Hooks
// ❌ 错误
import { useState } from 'react';
export default function MyComponent() {
const [count, setCount] = useState(0); // 💥 报错!
return <div>{count}</div>;
}
// ✅ 正确:提取到 Client Component
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
陷阱 2:大组件无脑 Client
把整个页面的根组件标记为 'use client',这等于放弃了 RSC 全部性能收益。
陷阱 3:滥用 Server Actions 做数据查询
Server Actions 设计用于写操作(表单提交等),不适合用于数据查询。在 Server Component 中直接查询数据库才是正确做法。
陷阱 4:忘记 Next.js 16 fetch 自动去重
同一渲染周期内,多个 Server Component 对同一 URL 的 fetch 请求自动去重,不会重复发送。
8. 性能基准对比
| 指标 | CSR | SSR (No RSC) | RSC + PPR | |------|-----|-------------|-----------| | FCP (首次内容绘制) | 2.1s | 1.4s | 0.6s | | LCP (最大内容绘制) | 3.8s | 2.2s | 0.9s | | TTI (可交互时间) | 4.5s | 2.8s | 1.5s | | JS Bundle (首屏) | 320 KB | 240 KB | 45 KB | | 总 JS Bundle | 520 KB | 380 KB | 150 KB |
测试环境:Vercel Pro, 全球 CDN,含图片的博客文章页。
9. 总结:RSC 改变了什么
React Server Components 不是一个性能优化技巧,而是一次架构范式的转变:
- 思维模型变了:从"组件在浏览器运行"变成"组件在最适合它的地方运行"
- 数据流简化了:Server → Client 单向数据流,不需要复杂的全局状态管理
- 性能预算释放了:服务端库的 JS 开销为零,可以大胆使用最佳工具
- 安全性提升了:API Key、数据库凭证、业务逻辑天然隔离在服务端
如果你还没在生产环境中拥抱 RSC,现在是时候了。Next.js 16 把这条路铺得很平。
正在将现有项目迁移到 Next.js 16 RSC 架构?我们提供从代码审查到全量迁移的技术咨询服务,确保迁移过程平滑且性能最大化。联系 WebMaster 获取免费架构评估。
相关阅读: