在学习 React 的过程中,我们通常会先接触到"组件""状态""事件""Hooks"这些概念。随着项目变大、对性能和 SEO 的要求越来越高,一个绕不开的话题就出现了:React 组件到底是在浏览器渲染,还是在服务器渲染?
这篇文章就围绕这个问题,系统梳理以下几个概念:
- 什么是客户端渲染(CSR)
- 传统 React 的问题在哪里
- 什么是 React Server Components
- 为什么不能直接在原生 React 中随便用 Server Components
- Next.js 中默认的 Server Component 和 use client 到底是什么意思
如果你刚开始接触 Next.js,或者最近听到了 React 19、Server Components、App Router 这些概念,但还没有完全理清,这篇文章可以帮你建立一个比较清楚的整体认识。
一、传统 React 是如何渲染页面的?
我们通常说 React 是一个用于构建用户界面的 JavaScript 库。更具体一点说,在传统使用方式下,React 主要运行在浏览器端。
一个普通 React 应用的大致流程如下:
-
首先,开发者编写 React 代码。开发完成后,再使用 webpack、Vite 之类的构建工具,把项目打包成适合上线部署的前端资源。随后,这些资源会被部署到服务器上。
-
当用户访问这个网站时,服务器通常返回的是一个非常基础的 HTML 文件。这个 HTML 文件往往只有一个像下面这样的结构:
<script src="/bundle.js"></script>
也就是说,最开始浏览器拿到的 HTML 几乎是空的,真正的页面内容并不在初始 HTML 里。然后浏览器继续下载 JavaScript bundle,执行 JavaScript 代码,React 才开始运行:
- 挂载组件
- 构建组件树
- 进行 reconciliation(协调)
- 最终把真正的 UI 渲染到 root 节点中
这个过程,就是我们常说的客户端渲染,也就是 CSR(Client Side Rendering)。
二、什么是客户端渲染(CSR)?
所谓客户端渲染,本质上就是:
页面内容不是服务器直接返回好的,而是浏览器拿到基础 HTML 后,再通过 JavaScript 动态生成页面内容。
从开发体验来看,这种方式非常灵活,也正是 React 流行起来的重要原因之一。
但客户端渲染也不是没有代价。随着项目复杂度增加,它的缺点会越来越明显。
三、传统 CSR 的两个主要问题
1. 初始 HTML 内容过少,不利于 SEO
CSR 最大的问题之一,就是首屏 HTML 往往过于空洞。
对于用户来说,这会导致一个常见现象:页面在 JavaScript 还没加载执行完成之前,可能会先出现短暂的空白。
对于搜索引擎来说,这个问题更明显。搜索引擎爬虫访问页面时,最先看到的是服务器返回的初始 HTML。如果这个 HTML 几乎没有内容,只有一个 div#root 和几个脚本标签,那么爬虫就很难准确理解页面内容。
比如一个商品列表页面,用户最终明明能看到"产品标题""商品列表""价格"等信息,但在初始 HTML 里,这些内容可能根本不存在。搜索引擎看到的只是一个空壳页面,这显然不利于 SEO 和自然流量获取。
2. JavaScript Bundle 过大,影响性能
另一个问题是性能。
随着 React 应用越来越复杂,前端 bundle 往往会越来越大。用户访问页面时,不仅要下载这些 JS,还要解析、执行,然后 React 才能真正把页面渲染出来。
这在桌面设备上有时还不那么明显,但在移动端或者弱网环境下,就很容易出现这些问题:
- 首屏显示慢
- 页面可交互时间变晚
- 用户感觉"页面加载出来很慢"
- 低性能设备体验下降
所以,传统 CSR 虽然开发方便,但在 SEO 和性能方面都存在天然短板。
四、React Server Components 是什么?
为了解决这些问题,React 引入了一个很重要的能力:++Server Components++。
它的核心思想并不复杂:
不是所有 React 组件都必须在浏览器里执行。某些组件完全可以先在服务器上运行,并直接生成 HTML。
也就是说,组件可以在服务端完成以下事情:
- 获取数据
- 运行组件逻辑
- 预渲染内容
- 生成 HTML
然后服务器把这份已经准备好的内容发给浏览器。浏览器的职责主要就变成了:显示这些内容。
这和传统 CSR 有一个根本区别:
- CSR:先给浏览器空 HTML,再靠 JS 动态生成 UI
- Server Components:服务器先把内容准备好,再把结果发给浏览器
五、Server Components 能解决什么问题?
1. 改善 SEO
因为页面内容已经在服务器上准备好了,所以搜索引擎爬虫访问页面时,拿到的是带内容的 HTML,而不是一个空壳。
这就使得页面更容易被正确抓取和索引。
2. 减少客户端 Bundle 体积
如果一个组件完全在服务器端执行,那么它的很多逻辑代码就不需要发送到浏览器。
这意味着:
- 客户端 JavaScript 更少
- 下载体积更小
- 浏览器执行压力更低
- 页面性能更好
所以,Server Components 的价值并不仅仅是"服务端渲染"这四个字,而是它能在架构层面减少前端负担。
六、React 已经有 Server Components 了,为什么还要 Next.js?
这里是很多初学者最容易混淆的地方。
虽然 Server Components 是 React 的能力,但React 本身并不是一个完整的服务端运行框架。传统意义上的 React 更像是一个专注于 UI 的库,它本身没有帮你搭好完整的服务端执行环境。
简单说就是:
React 提供了 Server Components 这种能力,但你还需要一个"能让这些组件在服务器运行起来"的框架。
而 Next.js 就是最典型的选择之一。
Next.js 构建在 React 之上,不只是一个前端路由工具,而是一个完整的 React 全栈框架。它能够:
- 在服务器运行 React 组件
- 处理服务端渲染
- 支持 App Router
- 默认支持 Server Components
所以,当你真正想在项目中使用 React Server Components 时,最常见的做法就是使用 Next.js。
七、在 Next.js 中,组件默认就是 Server Component
在 Next.js 的 App Router 模式下,一个很重要的默认规则是:
组件默认都是 Server Components。
也就是说,如果你在 app 目录里写一个普通组件,只要它没有使用客户端专属能力,那么它默认就会被当作服务端组件处理。
这意味着你可以在这些组件里:
- 获取服务端数据
- 渲染静态内容
- 输出 HTML
- 减少发送到客户端的 JS
这种默认行为,其实正是 Next.js 和传统 React 项目的一个重要区别。
八、什么时候需要 Client Component?
虽然 Server Components 很强,但不是所有组件都适合放在服务端。
如果一个组件需要这些能力:
- useState
- useEffect
- useRef
- 浏览器事件处理,比如 onClick
- 浏览器 API,比如 window、document
那么它就不能继续是一个纯服务端组件了。因为这些东西本质上需要在浏览器里运行。
这时,你就需要把它声明成 Client Component。
九、怎么把组件变成 Client Component?
方法非常简单:
在组件文件顶部加上一行:
"use client";
这是一条特殊指令。它告诉 Next.js:
这个组件需要在客户端运行。
比如下面这个组件里使用了 useState:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
如果没有 "use client",这个组件在 Next.js 中就会报错,因为 useState 只能在 Client Component 中使用。
十、Server Component 和 Client Component 的区别
可以把它们这样理解:
Server Component
- 默认运行在服务器
- 适合数据获取和预渲染
- 不适合交互逻辑
- 不直接使用 useState、useEffect、事件处理等客户端能力
- 不会把全部组件逻辑都发送到浏览器
Client Component
- 运行在浏览器
- 适合交互和状态管理
- 可以使用 Hooks 和事件
- 需要显式写 "use client"
所以在 Next.js 中,通常不是"全是服务端组件"或者"全是客户端组件",而是两者结合使用。
一个很典型的页面结构可能是:
页面主体、列表展示、数据获取:Server Components
按钮点击、表单状态、交互控件:Client Components
十一、为什么说 Next.js 是 React Server Components 的最佳实践场景?
因为 Next.js 帮你做了很多底层工作,让你不必从零搭建服务端环境。
比如你创建一个 Next.js 项目,使用 App Router 后:
- 页面组件默认就是 Server Components
- 可以直接写服务端组件逻辑
- 需要交互时,再用 "use client" 切换为客户端组件
这种模式非常自然,也很符合现代 React 项目的组织方式。
从开发者角度看,你不再需要把整个页面都丢给浏览器,而是可以更细粒度地思考:
- 哪部分应该在服务器完成?
- 哪部分必须在浏览器里交互?
- 哪部分代码没有必要进客户端 bundle?
这种"拆分渲染责任"的能力,就是 Server Components 带来的真正价值。
十二、一个简单的理解方式
如果你还是觉得这些概念有点绕,可以用一句话来记:
能在服务器提前准备好的内容,就尽量交给 Server Component。
必须依赖浏览器交互的部分,再交给 Client Component。
再压缩一点:
- 页面上的"内容"更适合 Server Components
- 页面上的"交互"更适合 Client Components
十四、什么是预渲染(Pre-rendering)?
预渲染,简单说就是:
在用户打开页面之前,先把页面的 HTML 内容准备好。
这里最关键的一点是:
用户一打开页面,浏览器拿到的 HTML 不是空壳,而是已经有页面结构和内容。
比如一个商品列表页,如果做了预渲染,那么服务器返回的 HTML 可能一开始就像这样:
<h1>Product List</h1>
<ul>
<li>iPhone</li>
<li>MacBook</li>
<li>AirPods</li>
</ul>
这样用户和搜索引擎一开始都能看到内容。
所以预渲染的本质不是"让页面能点击",而是:
先把页面内容准备出来。
它最主要解决的是:
- 首屏显示
- SEO
- 页面初始可见性
十五、预渲染和客户端渲染的区别
客户端渲染(CSR)
在 CSR 下,服务器最开始返回的 HTML 常常很空,比如:
<div id="root"></div>
<script src="/bundle.js"></script>
浏览器拿到后,要先:
- 下载 JS
- 执行 JS
- React 运行
- 创建组件树
- 渲染内容
也就是说:
内容是浏览器自己算出来的。
预渲染(Pre-rendering)
在预渲染下,服务器会先把页面 HTML 准备好,再发给浏览器。
所以浏览器一开始看到的就已经是内容了。
比如标题、列表、文本、图片结构等,都已经在 HTML 里。
也就是说:
内容是服务器先生成好的。
所以一句话区分:
- CSR:先空壳,后靠 JS 出内容。
- 预渲染:先有内容,再考虑交互。
十六、那什么是水合(Hydration)?
这个词最容易让人懵。
Hydration 直译叫"水合",但你不要按化学去想。
在 React 里,它的意思其实是:
浏览器接管服务器提前渲染好的 HTML,并给它绑定 React 的事件和状态逻辑。
你可以这样理解:
服务器已经帮你把页面"画出来了"。
但这时候页面还只是"静态长相"。
浏览器接下来下载 React 的 JS 代码,然后 React 会检查:
"这个 HTML 是不是和我组件渲染出来的一致?"
如果一致,React 就不会重新整个重画一遍,而是直接:
把事件绑定上去
把状态系统接上去
让组件变得可交互
这个过程,就叫 Hydration。
所以:
Hydration 不是重新渲染页面内容本身,而是让已有 HTML 变成一个真正由 React 控制、可交互的页面。
十七、为什么需要 Hydration?
因为服务器虽然能先把 HTML 给你生成出来,
但服务器不能替浏览器完成所有前端交互。
比如这些事情,最终还是得在浏览器里发生:
- 按钮点击
- 输入框输入
- 状态变化
- 弹窗开关
- 本地交互
- 浏览器事件监听
服务器只能先把"长什么样"给你。
浏览器还得负责"怎么动起来"。
所以 Hydration 的作用就是:
把"已经显示出来的静态页面",升级成"真正能交互的 React 页面"。
十八、一个最简单的例子
假设你有一个计数器组件:
function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
如果服务器先预渲染,它可能先输出这样一个 HTML:
<button>Count: 0</button>
用户打开页面时,已经能看见按钮了。
++但这时候如果 JS 还没加载完,这个按钮其实还不能真正响应 React 的状态逻辑。++
当浏览器把 React 代码下载并执行之后,React 会把这个按钮"接管"过来:
- 绑定 onClick
- 建立 count 的状态逻辑
- 让按钮点一下后能变成 Count: 1
这个"接管"的过程就是 Hydration。
所以你要注意:
Hydration 发生时,HTML 已经在页面上了。
它不是"生成 HTML",而是"让 HTML 活起来"。
十九、预渲染和 Hydration 的关系
你可以把它们理解成一个两步流程。
第一步:预渲染
服务器先生成 HTML
浏览器拿到后立刻能显示内容
第二步:Hydration
浏览器下载 React 的 JS
React 接管这份 HTML
页面获得交互能力
所以:
预渲染解决"先看到"
Hydration 解决"再能点"
这两个动作经常一起出现,但它们不是同一个东西。
二十、为什么很多人说"页面已经出来了,但还不能点"?
这其实就是预渲染和 Hydration 之间的时间差。
一个页面可能已经把 HTML 发出来了,所以你肉眼能看见内容;
但如果 JS 还没下载完,或者 Hydration 还没完成,那么某些交互可能还没准备好。
这时候就会出现这种现象:
- 页面已经显示了
- 但是按钮点击没反应
- 或者交互有短暂延迟
这说明:
HTML 已经预渲染完成,但 Hydration 还没彻底结束。
二十一、Server Components 和 Hydration 有什么关系?
这是你接下来在 Next.js 里特别容易混的点。
不是所有服务端出来的内容都需要完整 Hydration。
传统 SSR + 客户端 React
如果一个页面是服务端先渲染 HTML,再由 React 在客户端接管,那通常会有 Hydration 过程。
React Server Components
Server Components 更进一步,它的目标之一就是:
尽量减少需要发到客户端的 JavaScript。
也就是说:
- 有些组件纯粹在服务器运行
- 它们只负责产出内容
- 它们本身不会在浏览器里变成一个完整的可交互 React 组件
这种情况下,Hydration 的重点就主要落在那些真正的 Client Components 上。
你可以这样理解:
- Server Component:更像"纯内容输出"
- Client Component:更像"需要在浏览器里活起来的部分"
所以在 Next.js 里:
- 纯 Server Component 不强调浏览器交互
- 带 "use client" 的组件才更直接参与客户端状态和 Hydration
二十二、最容易混淆的几个概念
1. 预渲染 = 服务端渲染吗?
不完全等于,但关系很近。
预渲染强调的是:
HTML 在用户打开前就已经准备好。
这个 HTML 可能来自:
- 服务端实时渲染
- 构建时静态生成
- 某种服务器端预生成机制
所以预渲染是一个更大的概念。
2. Hydration = 再渲染一次吗?
不准确。
Hydration 不是"从零重新生成 HTML",
而是:
在已经存在的 HTML 上,补上 React 的运行能力。
所以更像"接管"和"激活",而不是单纯"重画"。
3. CSR 有 Hydration 吗?
通常我们谈严格意义上的 Hydration,更多是指:
页面已经有服务端或预生成 HTML,然后 React 在客户端接管。
纯 CSR 一开始几乎没内容,所以严格来说,它更多是"客户端首次渲染",不是"对现有 HTML 的水合"。
二十三、总结
React、Next.js、Server Components、预渲染和 Hydration 这些概念,初看起来很容易混在一起,但如果抓住"内容什么时候出来 "和"页面什么时候能交互"这两个核心问题,其实就会清楚很多。
传统 React 大多数场景下采用的是客户端渲染(CSR)。在这种模式下,服务器最开始返回的 HTML 往往只是一个空壳,真正的页面内容要等浏览器下载并执行 JavaScript 后,React 才会把 UI 渲染出来。这种方式开发灵活,但也带来了两个明显问题:一是初始 HTML 内容太少,不利于 SEO;二是随着项目变大,客户端 JavaScript bundle 也会越来越重,从而影响首屏性能和用户交互体验。
为了解决这些问题,React 引入了 Server Components。它的核心价值在于:让一部分组件可以直接在服务器执行,提前生成内容,再把结果发给浏览器。这样做的好处非常直接:搜索引擎更容易抓取页面内容,客户端也不必承担所有组件逻辑的执行成本,从而减轻浏览器负担。
不过,React 虽然提供了 Server Components 的能力,但它本身并不是一个完整的服务端运行框架。因此,在实际项目中,我们通常借助 Next.js 来真正落地这一能力。在 Next.js 的 App Router 中,组件默认就是 Server Component ;只有当组件需要使用 useState、useEffect、事件处理、浏览器 API 等客户端能力时,才需要通过 "use client" 将其显式声明为 Client Component。
与此同时,理解 预渲染(Pre-rendering) 和 Hydration(水合) 也很重要。预渲染解决的是"页面内容能不能先显示出来",也就是服务器能否提前把 HTML 内容准备好;Hydration 解决的是"这些已经显示出来的内容,什么时候真正拥有 React 的交互能力"。换句话说,预渲染负责先让页面"看得见",Hydration 负责再让页面"动起来"。
所以,整篇文章最核心的结论其实可以归纳成一句话:
现代 React/Next.js 的关键,不只是学会写组件,而是学会合理拆分:哪些内容应该在服务器提前完成,哪些交互必须留给浏览器处理。
当你真正理解了这点,你就不会再把 Server Component、Client Component、预渲染和 Hydration 看成几个彼此割裂的概念,而会把它们看成同一套渲染体系中的不同环节:
- CSR:内容主要靠浏览器执行 JS 后生成
- 预渲染:服务器提前把页面内容准备好
- Hydration:浏览器接管 HTML,让页面具备交互能力
- Server Component:适合做服务端内容输出
- Client Component:适合做浏览器交互和状态管理
理解这些概念之后,再去学习 Next.js 的 App Router、数据获取方式、页面性能优化和组件拆分策略,就会顺畅很多。