引言
React Server Components(RSC)是 React 18 引入的革命性架构——它让组件可以在服务器上执行,把数据库查询、文件系统访问等服务器能力直接带入组件层。与传统 SSR(服务端渲染 HTML)不同,RSC 服务端执行后输出的是「组件描述序列」(RSC Payload),由客户端 React 解析并渲染。这意味着:零客户端 JS 体积的纯服务端组件、自动数据去重、流式增量传输。本文从 RSC 核心概念出发,深入流式渲染、缓存策略、Server Action 与混合架构设计——给 Next.js App Router 时代的 React 开发者一份实战地图。
前置:前端性能优化基础
一、RSC 核心概念:与 SSR、CSR 的区别
1.1 三种渲染模式对比
| 维度 | CSR | SSR | RSC |
|---|---|---|---|
| 执行位置 | 浏览器 | 服务器(生成 HTML) | 服务器(生成组件描述) |
| 首屏 | 白屏+JS 加载 | 直接 HTML | HTML + 流式组件 |
| JS 体积 | 全量框架+业务 | 同 CSR(需 hydrate) | 服务端组件零 JS |
| 数据获取 | useEffect + API | getServerProps | 组件内直接 await |
| 交互 | 天然支持 | hydrate 后支持 | Server/Client 组件混合 |
1.2 RSC Payload 是什么
RSC 服务端执行后输出的不是 HTML,而是「组件树描述」:
{"type": "div", "props": {"children": [...]}}
或服务端组件引用:{"type": "$L1", ...}
客户端 React 解析这个 JSON-like 描述,构建虚拟 DOM,与现有树合并。
# 关键:Server Component 本身不打包到客户端 bundle 中
1.3 什么时候用 RSC
用 RSC:数据依赖重、不需要交互、SEO 重要的内容
用 Client Component:需要 useState/useEffect、DOM API、浏览器事件
# 默认所有组件都是服务端组件,需要客户端能力时加 'use client'
二、Server Component 执行环境与数据获取
2.1 执行环境
Server Component 在请求时于服务端执行:
- Node.js 运行时(Next.js 默认)
- Edge Runtime(V8 isolate,延迟更低)
- 可访问:fs、db、内部 API、环境变量
- 不可访问:window、document、localStorage
2.2 直接数据获取
// app/page.tsx — Server Component(默认)
import { db } from '@/lib/db';
export default async function Page() {
const posts = await db.query('SELECT * FROM posts');
return (
<main>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
2.3 fetch 自动去重
# 同一个渲染周期中,相同 URL 的 fetch 自动 dedupe
# React 内部缓存 fetch 结果,避免重复请求
# 注意:不使用 fetch(如 ORM 查询)需用 react.cache() 手动缓存
三、客户端组件边界:‘use client’ 与交互
3.1 ‘use client’ 指令
'use client';
import { useState } from 'react';
export default function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
);
}
3.2 混合架构:Server 包 Client
// Server Component 引入 Client Component
import LikeButton from './LikeButton';
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await db.query('SELECT * FROM posts WHERE id = ?', [params.id]);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<LikeButton postId={params.id} /> {/* Client Component */}
</article>
);
}
3.3 组件分层原则
# 叶子节点优先客户端化:
# 页面(Server)→ 布局(Server)→ 交互组件(Client)
# 不要过度 'use client':尽量把状态推到叶子
# Context Provider 需 'use client',但子组件可保持 Server
四、流式传输:Suspense 与 Streaming
4.1 Suspense 流式
import { Suspense } from 'react';
import { PostList } from './PostList';
import { PostListSkeleton } from './PostListSkeleton';
export default function Page() {
return (
<main>
<h1>My Blog</h1>
<Suspense fallback={<PostListSkeleton />}>
<PostList /> {/* 异步组件,流式渲染 */}
</Suspense>
</main>
);
}
4.2 Streaming 原理
服务器:
1) 渲染 Shell(不依赖异步数据的部分)→ 立即发送
2) 异步数据就绪 → 流式传输 Suspense 边界内的内容
3) 客户端:先显示 fallback,收到流后替换
关键:TTFB 快(Shell 先发)、FCP 快、LCP 在数据到后完成
4.3 选择性水合
React 18 的 Selective Hydration:
- 服务端已渲染的 HTML 先显示
- 用户交互的组件优先 hydrate
- 不交互的组件延迟 hydrate(on hover/scroll 时)
# 结合 RSC:服务端组件无需 hydrate,只有客户端组件需要
五、缓存策略:React Cache 与 Next.js fetch
5.1 fetch 缓存控制
// 默认:force-cache(同 URL 请求间缓存)
const data = await fetch('https://api.example.com/posts');
// 禁用缓存
const data = await fetch('https://api.example.com/posts', { cache: 'no-store' });
// ISR 式缓存(Next.js 扩展)
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // 60 秒后重新验证
});
5.2 react.cache
import { cache } from 'react';
const getUser = cache(async (id: string) => {
return db.query('SELECT * FROM users WHERE id = ?', [id]);
});
// 同一个渲染中多次调用自动去重
const user = await getUser('123');
const sameUser = await getUser('123'); // 返回缓存
5.3 缓存层级
Full Route Cache:整路由 HTML 缓存(Next.js)
Router Cache:客户端路由缓存(导航时不重新请求)
Data Cache:fetch 结果缓存(服务端)
Request Memoization:react.cache(请求级别)
六、Server Action 与表单突变
6.1 Server Action 定义
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title');
const content = formData.get('content');
await db.query('INSERT INTO posts (title, content) VALUES (?, ?)', [title, content]);
revalidatePath('/posts');
redirect('/posts');
}
6.2 表单中使用
// app/posts/new/page.tsx
import { createPost } from '@/app/actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<button type="submit">Create</button>
</form>
);
}
6.3 渐进增强
Server Action + 表单:
- 无 JS:表单正常提交(全页刷新)
- 有 JS:AJAX 提交 + 局部更新
# 天然支持渐进增强,无需额外代码
七、App Router 文件约定与路由设计
7.1 文件路由约定
app/
├── layout.tsx # 根布局(必须)
├── page.tsx # / 路由
├── loading.tsx # Suspense fallback 替代
├── error.tsx # Error Boundary
├── not-found.tsx # 404 页面
├── posts/
│ ├── page.tsx # /posts
│ ├── [id]/
│ │ ├── page.tsx # /posts/:id
│ │ └── layout.tsx # 嵌套布局
│ └── new/
│ └── page.tsx # /posts/new
7.2 Parallel Routes 与 Intercepting
@app/dashboard/page.tsx # /dashboard
@app/@modal/page.tsx # Parallel route(模态框)
Intercepting:
(app)/posts/[id]/page.tsx # 正常页面
@app/(.)posts/[id]/page.tsx # 拦截(从 / 打开时显示模态)
八、性能优化与常见陷阱
8.1 Payload 优化
# 减少 Server Component 输出体积:
# 1) 避免传递大量数据到 Client Component
# 2) 用 Server Component 做数据转换,只传必要字段
# 3) 图片用 next/image 自动优化
# 4) 第三方库用 Server Component 引入(不打包到客户端)
8.2 常见陷阱
# 1) 在 Server Component 中用浏览器 API → 报错
# 2) 在 Client Component 顶层 await → 需要 async 函数包裹
# 3) Context 跨边界:Server Component 不能用 React Context(需 'use client')
# 4) 过度 'use client':把数据获取放到 Server,只交互部分 Client
# 5) 忘记 revalidate:Server Action 后需刷新缓存
8.3 排错工具
# React DevTools:看组件是 Server 还是 Client
# Next.js DevOverlay:开发时错误提示
# Network Tab:看 RSC Payload 流式传输
# next build --analyze:分析客户端 bundle 体积
结语
React Server Components 不是替代 SSR 或 CSR,而是「第三维度」——让服务端组件与客户端组件在同一个应用中共存,各取所长。Server Component 负责数据获取和重型计算,Client Component 负责交互和浏览器 API,流式传输让首屏更快,Server Action 让表单突变更简单。掌握 ‘use client’ 边界、Suspense 流式、缓存分层这三把钥匙,你就能在 Next.js App Router 中游刃有余。随着 React 19 和 Server Actions 的成熟,RSC 正从「新特性」变成「默认范式」——现在投入学习,正当其时。
一句话记忆:RSC 服务器执行输出组件描述(非 HTML),零客户端 JS 体积;‘use client’ 标记客户端组件边界,Server Component 可直接 await 数据库查询;Suspense + Streaming 流式传输让首屏先现后丰;fetch 自动去重、react.cache 手动缓存;Server Action 让表单突变无 API 层、天然渐进增强;App Router 文件约定驱动路由(page/layout/loading/error);性能靠减少 Payload、减少 ‘use client’ 范围、next/image 优化;陷阱是浏览器 API 跨界、Context 需 client、忘记 revalidate——「默认服务端,按需客户端」是 RSC 的设计哲学。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。