本文基于 Next.js 官方文档 Server and Client Components 整理,覆盖使用场景、渲染原理、组合规则、最佳实践


一、核心定位

  • 默认类型:App Router 中 layout.tsx / page.tsx 默认为服务端组件(Server Components)
  • 分工原则:按运行环境拆分逻辑——服务端管渲染与数据,客户端管交互与浏览器 API。

二、使用场景(官方明确边界)

1. 客户端组件 Client Components(必须加 'use client'

满足以下任一需求时使用:

  • 需要组件状态useState / useReducer / useRef
  • 需要事件处理onClick / onChange
  • 需要生命周期useEffect
  • 需要浏览器专属 APIwindow / localStorage / navigator
  • 需要自定义 Hooks、React Context
  • 第三方交互组件(轮播、图表、富文本)

2. 服务端组件 Server Components(默认,无需标记)

满足以下任一需求时使用:

  • 就近获取数据:直连数据库、接口,无额外 API 层
  • 安全使用密钥:API Key、Token 不暴露到客户端
  • 减少客户端 JS 体积:服务端渲染,不发 JS 到浏览器
  • 提升首屏性能:优化 FCP,支持流式渲染
  • 纯展示、无交互的静态内容(文章、标题、列表)

三、渲染运行原理(官方流程)

1. 服务端渲染阶段

  • 服务端组件渲染为 RSC Payload(React 服务端组件二进制结构)
  • 客户端组件代码 + RSC 用于预渲染 HTML
  • 按路由分段(layout / page)拆分,支持流式传输

2. 客户端首次加载

  1. 先展示非交互 HTML,快速呈现内容
  2. 用 RSC 对齐服务端/客户端组件树
  3. 对客户端组件执行水合(hydration),绑定事件变可交互

3. 后续导航

  • 预加载 & 缓存 RSC,实现秒切路由
  • 客户端组件完全在浏览器渲染,不再服务端生成 HTML

关键概念:RSC Payload

  • 服务端组件渲染结果
  • 客户端组件占位与 JS 引用
  • 服务端 → 客户端的可序列化 props

四、基础写法规范

1. 声明方式

  • 服务端组件:无指令,默认就是
  • 客户端组件:文件顶部第一行加 'use client'(必须在所有 import 之前)
// 客户端组件示例
'use client'
import { useState } from 'react'
export default function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count+1)}>{count}</button>
}

2. 数据传递

  • 服务端 → 客户端:用 Props 传递(必须可序列化)
  • 客户端 → 服务端:不直接传组件,只能传可序列化数据

五、组件组合模式(官方推荐)

1. 服务端嵌套客户端(最常用)

  • 页面/布局(服务端)→ 嵌入交互按钮/表单(客户端)
  • 服务端取数,通过 props 传给客户端做交互
// page.tsx(服务端)
import LikeButton from './LikeButton'
export default async function Page({ params }) {
  const post = await getPost(params.id)
  return <LikeButton likes={post.likes} />
}

2. 客户端嵌套服务端

  • 客户端组件用 children 作为插槽
  • 服务端组件作为子元素传入,仍在服务端渲染
  • 示例:弹窗(客户端)包裹购物车(服务端)
// Modal(客户端)
'use client'
export default function Modal({ children }) {
  return <div>{children}</div>
}

// Page(服务端)
import Modal from './Modal'
import Cart from './Cart' // 服务端组件
export default function Page() {
  return <Modal><Cart /></Modal>
}

3. Context 提供者

  • Context 只能在客户端组件创建
  • 在根布局(服务端)引入包裹全局,实现全局状态共享
// ThemeProvider(客户端)
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext()
export default function ThemeProvider({ children }) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}

// 根布局(服务端)
import ThemeProvider from './ThemeProvider'
export default function RootLayout({ children }) {
  return <html><body><ThemeProvider>{children}</ThemeProvider></body></html>
}

4. 第三方组件兼容

  • 依赖客户端特性的库,需包裹一层 'use client' 再使用
  • 库作者应在入口加 'use client' 方便用户直接使用

六、性能与安全最佳实践

  1. 最小化客户端边界
    只给真正需要交互的组件加 'use client',避免整页变成客户端组件,减小 bundle 体积。

  2. 环境隔离防污染

    • 服务端代码:用 server-only 包防止被引入客户端
    • 客户端代码:可用 client-only 标记浏览器专属逻辑
    • 环境变量:非 NEXT_PUBLIC_ 前缀不会注入客户端,防密钥泄露
  3. Provider 尽量下沉
    不要包裹整个 html,缩小客户端渲染范围,优化服务端静态内容缓存


七、快速判断口诀(官方思路)

  • 交互、状态、浏览器 API → 客户端组件('use client'
  • 取数、安全、减包、提速 → 服务端组件(默认)
  • 服务端管数据与渲染,客户端管交互,组合使用最稳

八、核心对比表

维度 服务端组件 客户端组件
声明 默认,无指令 必须 'use client'
渲染位置 服务端 客户端
交互能力 完整(state/effect/事件)
数据获取 直连数据库/API 只能走接口
安全密钥 可安全使用 不可暴露
客户端 JS 零打包 需发送 JS
首屏性能 一般
适用场景 展示、取数、SEO 交互、状态、浏览器 API
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐