server-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React 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
'use client'
directive for interactivity:
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
    ,
    useEffect
    ,
    useReducer
  • 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
    useEffect
    useReducer
  • 事件处理函数(
    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:
  1. Server Components can import Client Components
  2. Client Components cannot import Server Components
  3. You can pass Server Components as
    children
    to Client Components
可以将组件树视为存在一个“客户端边界”:
Server Component (page.tsx)
├── Server Component (header.tsx)
├── Client Component ('use client') ← boundary
│   ├── Client Component (child)
│   └── Client Component (child)
└── Server Component (footer.tsx)
核心规则:
  1. 服务端组件可以导入客户端组件
  2. 客户端组件不能导入服务端组件
  3. 可以将服务端组件作为
    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

常见错误

  1. Don't add
    'use client'
    unnecessarily - it increases bundle size
  2. Don't try to import Server Components into Client Components
  3. Do serialize data at boundaries (no functions, classes, or dates)
  4. Do use the children pattern for composition
  1. 请勿不必要地添加
    'use client'
    - 这会增加包体积
  2. 请勿尝试在客户端组件中导入服务端组件
  3. 在边界处序列化数据(不能传递函数、类或日期对象)
  4. 使用 children 模式进行组件组合

Resources

资源

For detailed patterns, see:
  • references/server-vs-client.md
    - Complete comparison guide
  • references/composition-patterns.md
    - Advanced composition
  • examples/data-fetching-patterns.md
    - Data fetching examples
如需了解详细模式,请参阅:
  • references/server-vs-client.md
    - 完整对比指南
  • references/composition-patterns.md
    - 高级组合技巧
  • examples/data-fetching-patterns.md
    - 数据获取示例