server-components
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Server Components in Next.js
Next.js 中的 React Server Components
Overview
概述
React Server Components (RSC) allow components to render on the server, reducing client-side JavaScript and enabling direct data access. In Next.js App Router, all components are Server Components by default.
React Server Components(RSC)允许组件在服务端渲染,减少客户端 JavaScript 代码量并支持直接访问数据。在 Next.js App Router 中,所有组件默认都是 Server Components。
Server vs Client Components
服务端组件 vs 客户端组件
Server Components (Default)
服务端组件(默认)
Server Components run only on the server:
tsx
// app/users/page.tsx (Server Component - default)
async function UsersPage() {
const users = await db.user.findMany() // Direct DB access
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}Benefits:
- Direct database/filesystem access
- Keep sensitive data on server (API keys, tokens)
- Reduce client bundle size
- Automatic code splitting
服务端组件仅在服务端运行:
tsx
// app/users/page.tsx (Server Component - default)
async function UsersPage() {
const users = await db.user.findMany() // Direct DB access
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}优势:
- 直接访问数据库/文件系统
- 敏感数据(API 密钥、令牌)保留在服务端
- 减小客户端包体积
- 自动代码分割
Client Components
客户端组件
Add directive for interactivity:
'use client'tsx
// components/counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}Use Client Components for:
- ,
useState,useEffectuseReducer - Event handlers (,
onClick)onChange - Browser APIs (,
window)document - Custom hooks with state
添加 指令以实现交互性:
'use client'tsx
// components/counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}客户端组件适用于:
- 、
useState、useEffectuseReducer - 事件处理函数(、
onClick)onChange - 浏览器 API(、
window)document - 带状态的自定义 Hooks
The Mental Model
心智模型
Think of the component tree as having a "client boundary":
Server Component (page.tsx)
├── Server Component (header.tsx)
├── Client Component ('use client') ← boundary
│ ├── Client Component (child)
│ └── Client Component (child)
└── Server Component (footer.tsx)Key rules:
- Server Components can import Client Components
- Client Components cannot import Server Components
- You can pass Server Components as to Client Components
children
可以将组件树视为存在一个“客户端边界”:
Server Component (page.tsx)
├── Server Component (header.tsx)
├── Client Component ('use client') ← boundary
│ ├── Client Component (child)
│ └── Client Component (child)
└── Server Component (footer.tsx)核心规则:
- 服务端组件可以导入客户端组件
- 客户端组件不能导入服务端组件
- 可以将服务端组件作为 属性传递给客户端组件
children
Composition Patterns
组合模式
Pattern 1: Server Data → Client Interactivity
模式 1:服务端数据 → 客户端交互
Fetch data in Server Component, pass to Client:
tsx
// app/products/page.tsx (Server)
import { ProductList } from './product-list'
export default async function ProductsPage() {
const products = await getProducts()
return <ProductList products={products} />
}
// app/products/product-list.tsx (Client)
'use client'
export function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('')
const filtered = products.filter(p =>
p.name.includes(filter)
)
return (
<>
<input onChange={e => setFilter(e.target.value)} />
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</>
)
}在服务端组件中获取数据,传递给客户端组件:
tsx
// app/products/page.tsx (Server)
import { ProductList } from './product-list'
export default async function ProductsPage() {
const products = await getProducts()
return <ProductList products={products} />
}
// app/products/product-list.tsx (Client)
'use client'
export function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('')
const filtered = products.filter(p =>
p.name.includes(filter)
)
return (
<>
<input onChange={e => setFilter(e.target.value)} />
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</>
)
}Pattern 2: Children as Server Components
模式 2:将服务端组件作为 Children 传递
Pass Server Components through children prop:
tsx
// components/client-wrapper.tsx
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children} {/* Server Component content */}
</div>
)
}
// app/page.tsx (Server)
import { ClientWrapper } from '@/components/client-wrapper'
import { ServerContent } from '@/components/server-content'
export default function Page() {
return (
<ClientWrapper>
<ServerContent /> {/* Renders on server! */}
</ClientWrapper>
)
}通过 children 属性传递服务端组件:
tsx
// components/client-wrapper.tsx
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children} {/* Server Component content */}
</div>
)
}
// app/page.tsx (Server)
import { ClientWrapper } from '@/components/client-wrapper'
import { ServerContent } from '@/components/server-content'
export default function Page() {
return (
<ClientWrapper>
<ServerContent /> {/* Renders on server! */}
</ClientWrapper>
)
}Pattern 3: Slots for Complex Layouts
模式 3:复杂布局的插槽
Use multiple children slots:
tsx
// components/dashboard-shell.tsx
'use client'
interface Props {
sidebar: React.ReactNode
main: React.ReactNode
}
export function DashboardShell({ sidebar, main }: Props) {
const [collapsed, setCollapsed] = useState(false)
return (
<div className="flex">
{!collapsed && <aside>{sidebar}</aside>}
<main>{main}</main>
</div>
)
}使用多个子插槽:
tsx
// components/dashboard-shell.tsx
'use client'
interface Props {
sidebar: React.ReactNode
main: React.ReactNode
}
export function DashboardShell({ sidebar, main }: Props) {
const [collapsed, setCollapsed] = useState(false)
return (
<div className="flex">
{!collapsed && <aside>{sidebar}</aside>}
<main>{main}</main>
</div>
)
}Data Fetching
数据获取
Async Server Components
异步服务端组件
Server Components can be async:
tsx
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}服务端组件可以是异步的:
tsx
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}Parallel Data Fetching
并行数据获取
Fetch multiple resources in parallel:
tsx
export default async function DashboardPage() {
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
])
return (
<Dashboard user={user} posts={posts} analytics={analytics} />
)
}并行获取多个资源:
tsx
export default async function DashboardPage() {
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
])
return (
<Dashboard user={user} posts={posts} analytics={analytics} />
)
}Streaming with Suspense
使用 Suspense 进行流式渲染
Stream slow components:
tsx
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<PostsSkeleton />}>
<SlowPosts /> {/* Streams when ready */}
</Suspense>
</div>
)
}流式渲染加载缓慢的组件:
tsx
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<PostsSkeleton />}>
<SlowPosts /> {/* Streams when ready */}
</Suspense>
</div>
)
}Decision Guide
决策指南
Use Server Component when:
- Fetching data
- Accessing backend resources
- Keeping sensitive info on server
- Reducing client JavaScript
- Component has no interactivity
Use Client Component when:
- Using state (,
useState)useReducer - Using effects ()
useEffect - Using event listeners
- Using browser APIs
- Using custom hooks with state
使用服务端组件的场景:
- 获取数据
- 访问后端资源
- 敏感信息保留在服务端
- 减少客户端 JavaScript 代码量
- 组件无交互需求
使用客户端组件的场景:
- 使用状态(、
useState)useReducer - 使用副作用()
useEffect - 使用事件监听器
- 使用浏览器 API
- 使用带状态的自定义 Hooks
Common Mistakes
常见错误
- Don't add unnecessarily - it increases bundle size
'use client' - Don't try to import Server Components into Client Components
- Do serialize data at boundaries (no functions, classes, or dates)
- Do use the children pattern for composition
- 请勿不必要地添加 - 这会增加包体积
'use client' - 请勿尝试在客户端组件中导入服务端组件
- 请在边界处序列化数据(不能传递函数、类或日期对象)
- 请使用 children 模式进行组件组合
Resources
资源
For detailed patterns, see:
- - Complete comparison guide
references/server-vs-client.md - - Advanced composition
references/composition-patterns.md - - Data fetching examples
examples/data-fetching-patterns.md
如需了解详细模式,请参阅:
- - 完整对比指南
references/server-vs-client.md - - 高级组合技巧
references/composition-patterns.md - - 数据获取示例
examples/data-fetching-patterns.md