React Server Components (RSC) 代表了自 hooks 以来 React 最大的架构转变。它们允许组件专门在服务器上运行,实现直接数据库访问、服务器组件零客户端 JavaScript 和自动代码分割。
什么是 React Server Components?
React Server Components 是在服务器上渲染并将输出发送到客户端的组件。与传统 SSR 不同,Server Components 不会将其 JavaScript 发送到浏览器。
在 RSC 模型中,有三种类型的组件:Server Components(默认)、Client Components(标记 "use client")和 Shared Components。
Server vs Client Components
| 方面 | Server Component | Client Component |
|---|---|---|
| App Router 默认 | 是(默认) | 否(选择加入) |
| 需要的指令 | 无 | "use client" |
| 发送到客户端的 JS | 无(零 JS) | 是 |
| useState / useEffect | 不可用 | 可用 |
| 事件处理器 | 不可用 | 可用 |
| 直接数据库访问 | 是 | 否 |
| 文件系统访问 | 是 | 否 |
| 服务端环境变量 | 是 | 否 |
| 异步组件 | 是 | 否 |
| 包大小影响 | 零 | 增加包大小 |
决策树:Server 还是 Client?
Should this component be a Server or Client Component?
Does it need useState, useEffect, or useRef?
├── YES → Client Component ("use client")
└── NO ↓
Does it need event handlers (onClick, onChange, onSubmit)?
├── YES → Client Component ("use client")
└── NO ↓
Does it use browser-only APIs (window, localStorage, navigator)?
├── YES → Client Component ("use client")
└── NO ↓
Does it use third-party libraries that require hooks or browser APIs?
├── YES → Client Component ("use client")
└── NO ↓
Does it fetch data or access databases/file system?
├── YES → Server Component (default) ← BEST CHOICE
└── NO → Server Component (default) ← still the defaultRSC 模式
模式 1:服务端数据获取
Server Components 可以直接使用 async/await 获取数据。
// app/users/page.tsx — Server Component (default, no directive needed)
import { db } from '@/lib/database';
// This component is async — only possible in Server Components
export default async function UsersPage() {
// Direct database query — this code NEVER reaches the client
const users = await db.user.findMany({
orderBy: { createdAt: 'desc' },
take: 50,
});
return (
<div>
<h1>Users ({users.length})</h1>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
</div>
);
}
// Benefits:
// - Zero JavaScript sent to client for this component
// - No loading states needed (data is available before render)
// - Database credentials never exposed to the browser
// - No useEffect + fetch + useState dance模式 2:Server-Client 组合
Server Components 可以渲染 Client Components。Client Components 可以接受 Server Components 作为 children。
// components/LikeButton.tsx — Client Component
'use client';
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(0);
return (
<button onClick={() => {
setLiked(!liked);
setCount(c => liked ? c - 1 : c + 1);
}}>
{liked ? 'Unlike' : 'Like'} ({count})
</button>
);
}
// app/posts/[id]/page.tsx — Server Component
import { db } from '@/lib/database';
import { LikeButton } from '@/components/LikeButton';
export default async function PostPage({ params }: { params: { id: string } }) {
// Server: fetch data
const post = await db.post.findUnique({ where: { id: params.id } });
return (
<article>
{/* Server-rendered content (zero JS) */}
<h1>{post?.title}</h1>
<div>{post?.content}</div>
{/* Client Component island (only this ships JS) */}
<LikeButton postId={params.id} />
</article>
);
}
// Pattern: Server Component as children of Client Component
// components/Sidebar.tsx
'use client';
export function Sidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true);
return (
<aside style={{ display: open ? 'block' : 'none' }}>
<button onClick={() => setOpen(!open)}>Toggle</button>
{children} {/* Server Component content passed as children */}
</aside>
);
}模式 3:Server Actions
Server Actions 允许 Client Components 直接调用服务端函数。
// app/actions.ts — Server Action
'use server';
import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.post.create({
data: { title, content, authorId: 'user-1' },
});
revalidatePath('/posts');
}
export async function deletePost(postId: string) {
await db.post.delete({ where: { id: postId } });
revalidatePath('/posts');
}
// components/CreatePostForm.tsx — Client Component using Server Action
'use client';
import { createPost } from '@/app/actions';
import { useActionState } from 'react';
export function CreatePostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" required />
<textarea name="content" placeholder="Write your post..." required />
<button type="submit">Create Post</button>
</form>
);
}
// Works without API routes!
// The form submits directly to the server action
// Progressive enhancement: works without JavaScript模式 4:Suspense 流式传输
支持流式传输,慢的数据获取不会阻塞整个页面。
// app/dashboard/page.tsx — Streaming with Suspense
import { Suspense } from 'react';
// Slow component — fetches from external API
async function AnalyticsChart() {
const data = await fetch('https://api.analytics.com/data', {
next: { revalidate: 60 },
});
const analytics = await data.json();
return <div>Chart: {analytics.visitors} visitors</div>;
}
// Fast component — fetches from local DB
async function RecentPosts() {
const posts = await db.post.findMany({ take: 5 });
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* This renders immediately */}
<Suspense fallback={<div>Loading posts...</div>}>
<RecentPosts />
</Suspense>
{/* This streams in when ready (doesn't block the page) */}
<Suspense fallback={<div>Loading analytics...</div>}>
<AnalyticsChart />
</Suspense>
</div>
);
}
// Result: User sees "Dashboard" + posts immediately
// Analytics chart streams in when the API responds
// No client-side loading spinners needed数据获取策略
顺序数据获取
当请求相互依赖时使用顺序获取。
// Sequential: second request depends on first
async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId); // 1st request
const posts = await getUserPosts(user.name); // 2nd (depends on user.name)
return <div>{user.name}: {posts.length} posts</div>;
}并行数据获取
当请求独立时,使用 Promise.all 并行获取。
// Parallel: independent requests — use Promise.all
async function Dashboard() {
// Start all requests simultaneously
const [users, posts, analytics] = await Promise.all([
getUsers(),
getPosts(),
getAnalytics(),
]);
return (
<div>
<UserList users={users} />
<PostList posts={posts} />
<AnalyticsPanel data={analytics} />
</div>
);
}缓存和重新验证
Next.js 扩展了 fetch 的缓存控制。
// Next.js fetch caching strategies
// 1. Static (cached indefinitely until revalidated)
const data = await fetch('https://api.example.com/data');
// Equivalent to: { cache: 'force-cache' }
// 2. Revalidate every 60 seconds (ISR)
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 },
});
// 3. Dynamic (no caching — fresh on every request)
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
});
// 4. Tag-based revalidation
const data = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
});
// In a Server Action:
import { revalidateTag } from 'next/cache';
revalidateTag('posts'); // Purge all fetches tagged 'posts'性能优势
RSC 相比传统客户端 React 提供多项性能优势:
RSC Performance Benefits:
1. Zero Client JavaScript for Server Components
- Only Client Components ship JS to the browser
- Large libraries used on server (markdown, syntax highlighting) add zero bundle
2. Streaming SSR
- HTML streams to browser as components resolve
- First Contentful Paint (FCP) is faster
- Time to Interactive (TTI) is faster (less JS to hydrate)
3. Automatic Code Splitting
- Each Client Component is automatically code-split
- No manual dynamic imports needed
4. Reduced Data Transfer
- Server Components fetch data on the server (same network)
- No client-side fetch waterfalls
- No duplicate data in both HTML and JS payload
5. Smaller Hydration Payload
- Only Client Components need hydration
- Server Component output is static — no hydration needed
Typical Results:
Before RSC: 450KB JS bundle, 2.5s TTI
After RSC: 180KB JS bundle, 1.1s TTI (60% JS reduction)常见错误
// Mistake 1: Adding "use client" unnecessarily
// BAD: This doesn't need client-side JavaScript
'use client'; // REMOVE THIS
export function UserCard({ name, email }: { name: string; email: string }) {
return <div>{name} ({email})</div>; // No hooks, no events = Server Component
}
// Mistake 2: Importing Server Component in Client Component
// BAD: This won't work
'use client';
import { ServerComponent } from './ServerComponent'; // ERROR
// FIX: Pass as children instead
export function ClientWrapper({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}
// Mistake 3: Using "use client" too high in the tree
// BAD: Making a layout a Client Component
'use client'; // This makes EVERYTHING below it client-side
export function Layout({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('dark');
return <div className={theme}>{children}</div>;
}
// FIX: Extract the interactive part into a small Client Component
// components/ThemeToggle.tsx
'use client';
export function ThemeToggle() {
const [theme, setTheme] = useState('dark');
return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme}</button>;
}
// layout.tsx — stays as Server Component
export function Layout({ children }: { children: React.ReactNode }) {
return <div><ThemeToggle />{children}</div>;
}常见问题
可以不用 Next.js 使用 Server Components 吗?
技术上可以,但实际上 Next.js 是 RSC 的主要生产就绪框架。
Server Components 替代 SSR 吗?
不。Server Components 补充 SSR。
何时使用 "use client"?
当组件需要 hooks、事件处理器、浏览器 API 时。
Server Components 可以使用 context 吗?
不可以。React Context 是客户端功能。
Server Components 如何影响 SEO?
正面影响。在服务器上产生 HTML,搜索引擎可以爬取。
相关工具和指南
- JSON Formatter - Format API responses and data
- JSON to TypeScript - Generate types from API responses
- HTML to JSX - Convert HTML to React JSX
- Next.js App Router Guide
- React Hooks Guide
- TypeScript Generics Guide
- Web Performance Optimization