React Server Components 实战:工具库网站的 RSC 架构实践

技术架构(更新于 2026年5月21日)

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 获取数据
  • 导入只在服务端可用的模块(如 fsgray-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——这是实现极致首屏性能的关键架构决策。

本站提供浏览器本地工具,免注册即可试用 →

#React#Server Components#Next.js#SSR#性能