React Server Components 实战:工具库网站的 RSC 架构实践
Server Components 解决了什么问题?
传统 React 应用的痛点:
浏览器下载 JS bundle → 解析执行 React → 发起 API 请求 → 渲染 UI
↑ 慢 ↑ 阻塞 ↑ 瀑布流
每个页面都需要下载完整的 React 运行时 + 组件代码 + 数据获取逻辑。对于内容为主的工具站,大量 JS 是不必要的。
React Server Components (RSC) 将组件渲染移到服务器(或构建时),浏览器只接收 HTML + 最小化的 Client Component JS。
渲染模型对比
| 模式 | 渲染位置 | JS 发送到浏览器 | 数据获取 |
|---|---|---|---|
| CSR | 浏览器 | 全部组件 JS | useEffect 瀑布流 |
| SSR | 服务器 → HTML | 全部组件 JS(hydration) | getServerSideProps |
| RSC | 服务器/构建时 | 仅 Client Component JS | 组件内直接 async/await |
| SSG | 构建时 → HTML | 仅 Client Component JS | 构建时获取 |
工具库使用 SSG + RSC:构建时渲染所有 Server Component,Client Component 按需加载。
Server vs Client Component 边界
默认规则:一切都是 Server Component
// ✅ Server Component(默认)— 无 'use client'
export default async function BlogPage({ params }) {
const posts = getAllPosts(params.locale, 'blog'); // 直接读文件系统
return (
<div>
<h1>{posts.length} 篇文章</h1>
{posts.map(p => <PostCard key={p.slug} post={p} />)}
</div>
);
}
Server Component 可以:
- 直接访问文件系统、数据库
- 使用 async/await 获取数据
- 导入只在服务端可用的模块(如
fs、gray-matter)
Server Component 不能:
- 使用 useState、useEffect 等 Hooks
- 绑定事件处理器(onClick 等)
- 访问浏览器 API(window、localStorage)
何时需要 Client Component
'use client'; // ← 标记为 Client Component
import { useState } from 'react';
export function PdfMergeClient() {
const [files, setFiles] = useState<File[]>([]);
// 需要交互、状态、浏览器 API → Client Component
}
工具库的划分原则:
| 组件类型 | 示例 | 原因 |
|---|---|---|
| Server | 博客列表、教程页、导航栏 | 静态内容,零 JS |
| Client | PDF 合并、JSON 格式化、视频转码 | 需要文件 API 和交互 |
| Server + Client 组合 | 工具页 layout (Server) + 工具 UI (Client) | 最小化 JS |
工具库的 RSC 架构
页面结构
src/app/[locale]/pdf/merge/page.tsx ← Server Component
├── generateMetadata() ← SEO 元数据(构建时)
├── ToolPageLayout ← Server Component(面包屑、标题)
└── PdfMergeClient.tsx ← Client Component('use client')
├── 文件上传 UI
├── pdf-lib 处理逻辑
└── 下载按钮
代码分割效果
传统 CSR 页面 JS: ~180KB (React + 工具逻辑 + UI 组件)
RSC 页面 JS: ~45KB (仅 Client Component + 工具逻辑)
减少: ~75%
Server Component 渲染的 HTML(标题、描述、面包屑、FAQ)零 JS 开销。
数据获取:构建时 vs 运行时
// 博客文章:构建时读取 MDX 文件
export default async function BlogPostPage({ params }) {
const { slug, locale } = await params;
const result = getRenderedPost(slug, locale, 'blog'); // 读文件系统
if (!result) notFound();
return <ContentPostArticle post={result.post} rendered={result.rendered} />;
}
// 工具页:构建时加载 i18n 消息
export default async function PdfMergePage({ params }) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'tools.pdf_merge' });
return (
<ToolPageLayout title={t('title')} description={t('description')}>
<PdfMergeClient />
</ToolPageLayout>
);
}
所有数据在 next build 时获取并渲染为静态 HTML——运行时零数据请求。
性能收益实测
工具库首页(zh-CN)的资源加载对比:
| 指标 | 纯 CSR 估算 | RSC + SSG 实测 |
|---|---|---|
| 首屏 JS | ~180KB | ~12KB |
| FCP | ~1.2s | ~0.4s |
| LCP | ~2.1s | ~0.8s |
| TTI | ~2.8s | ~1.2s |
| HTML 大小 | ~5KB (空壳) | ~45KB (完整内容) |
HTML 变大但 JS 大幅减小——对于内容为主的工具站,这是正确的权衡。搜索引擎和用户都能立即看到完整内容。
常见陷阱
1. 意外引入 Client Component 边界
// ❌ Server Component 导入了 Client Component 的依赖
import { someUtil } from './client-utils'; // 如果 client-utils 有 'use client'
// ✅ 将共享逻辑提取到独立的 server-safe 模块
import { someUtil } from './shared-utils';
'use client' 是模块级指令,导入 Client Component 模块会将整个导入链标记为 client。
2. 在 Server Component 中使用 Hooks
// ❌ 编译错误
export default function Page() {
const [state, setState] = useState(0); // Hooks 只能在 Client Component 中使用
}
// ✅ 拆分为 Server + Client
export default function Page() {
return <InteractivePart />; // 'use client' 组件
}
3. 过度 Client 化
// ❌ 整个页面都是 Client Component
'use client';
export default function ToolPage() {
return (
<div>
<h1>PDF 合并</h1> {/* 静态标题,不需要 client */}
<p>合并多个 PDF 文件</p> {/* 静态描述,不需要 client */}
<PdfMerger /> {/* 只有这个需要 client */}
</div>
);
}
// ✅ 最小化 Client 边界
export default function ToolPage() {
return (
<div>
<h1>PDF 合并</h1>
<p>合并多个 PDF 文件</p>
<PdfMerger /> {/* 只有这个组件有 'use client' */}
</div>
);
}
RSC 与静态导出的结合
工具库使用 output: "export" 静态导出,RSC 在构建时渲染:
next build
→ 遍历 generateStaticParams()
→ 每个 (locale, path) 执行 Server Component
→ 输出静态 HTML + Client Component JS chunks
→ 部署到 CDN
这意味着 RSC 的运行时优势(减少服务器负载)在静态导出中转化为更小的 JS bundle 和更快的首屏。
总结
React Server Components 不是替代 Client Components,而是重新定义了 JS 的必要边界。对于工具库这类内容为主、交互为辅的网站,RSC 让 90% 的页面内容零 JS 渲染,仅工具操作区域加载必要的 Client Component——这是实现极致首屏性能的关键架构决策。
本站提供浏览器本地工具,免注册即可试用 →