Next.js 客户端组件(Client Components)与服务端组件(Server Components)详解
Next.js 的客户端组件与服务端组件架构是 React 生态的重大变革,通过**“服务器负责数据与渲染,客户端负责交互与体验”**的分工模式,实现了性能与交互的完美平衡。理解两种组件的核心差异和适用场景,是构建高效 Next.js 应用的关键。优先使用服务端组件处理数据和静态内容,仅在需要交互时使用客户端组件,并通过合理的组件拆分和组合,最大化应用性能和用户体验。
·
本文基于 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 - 需要浏览器专属 API:
window/localStorage/navigator - 需要自定义 Hooks、React Context
- 第三方交互组件(轮播、图表、富文本)
2. 服务端组件 Server Components(默认,无需标记)
满足以下任一需求时使用:
- 就近获取数据:直连数据库、接口,无额外 API 层
- 安全使用密钥:API Key、Token 不暴露到客户端
- 减少客户端 JS 体积:服务端渲染,不发 JS 到浏览器
- 提升首屏性能:优化 FCP,支持流式渲染
- 纯展示、无交互的静态内容(文章、标题、列表)
三、渲染运行原理(官方流程)
1. 服务端渲染阶段
- 服务端组件渲染为 RSC Payload(React 服务端组件二进制结构)
- 客户端组件代码 + RSC 用于预渲染 HTML
- 按路由分段(layout / page)拆分,支持流式传输
2. 客户端首次加载
- 先展示非交互 HTML,快速呈现内容
- 用 RSC 对齐服务端/客户端组件树
- 对客户端组件执行水合(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'方便用户直接使用
六、性能与安全最佳实践
-
最小化客户端边界
只给真正需要交互的组件加'use client',避免整页变成客户端组件,减小 bundle 体积。 -
环境隔离防污染
- 服务端代码:用
server-only包防止被引入客户端 - 客户端代码:可用
client-only标记浏览器专属逻辑 - 环境变量:非
NEXT_PUBLIC_前缀不会注入客户端,防密钥泄露
- 服务端代码:用
-
Provider 尽量下沉
不要包裹整个html,缩小客户端渲染范围,优化服务端静态内容缓存
七、快速判断口诀(官方思路)
- 要交互、状态、浏览器 API → 客户端组件(
'use client') - 要取数、安全、减包、提速 → 服务端组件(默认)
- 服务端管数据与渲染,客户端管交互,组合使用最稳
八、核心对比表
| 维度 | 服务端组件 | 客户端组件 |
|---|---|---|
| 声明 | 默认,无指令 | 必须 'use client' |
| 渲染位置 | 服务端 | 客户端 |
| 交互能力 | 无 | 完整(state/effect/事件) |
| 数据获取 | 直连数据库/API | 只能走接口 |
| 安全密钥 | 可安全使用 | 不可暴露 |
| 客户端 JS | 零打包 | 需发送 JS |
| 首屏性能 | 优 | 一般 |
| 适用场景 | 展示、取数、SEO | 交互、状态、浏览器 API |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)