今儿部门新人问我:"已经在页面里写明了use client了,为什么还是报错 Hydration failed 啊?"
原因是他在代码里写了typeof window !== 'undefined'。
下面我会把 为什么 use client 仍然会出现 Hydration failed 这个问题完整讲清楚,因为这是 Next.js 里非常常见的误解。
✅ 结论先说:use client ≠ 只在客户端运行
这是大家最容易误解的点。
✔ use client 的真正含义:
"让这个组件在客户端 可以使用 React 的交互功能(如 useState/useEffect)"
但 组件本身仍然会在服务端执行一次来生成 HTML(SSR) ,然后再在客户端 hydration。
所以:
- 你写了
use client - 但组件仍然 会在服务端运行一次(Node 环境)
- 服务端渲染出来的 HTML 会发送给浏览器
- 浏览器再执行相同的 React 代码进行 hydration
⚠ 因此,即使是 Client Component,它的 模块初始化代码(顶层代码)仍然会在服务器跑一次。
❌ 因此:顶层使用 window 依旧会报错
你写:
ts
if (typeof window !== 'undefined') {
...
}
这个语句依旧会被 服务端执行一次(因为 SSR 需要它),虽然不会崩溃,但关键问题是
❌ SSR 输出的 HTML 与 CSR 输出的 HTML 不一致 → hydration fail
示例:
tsx
"use client"
export default function Page() {
let text = "hello";
if (typeof window !== "undefined") {
text = "hello from client";
}
return <div>{text}</div>;
}
服务器执行时:
typeof window === 'undefined'- text =
"hello"
浏览器执行 hydration 时:
typeof window !== 'undefined'- text =
"hello from client"
最终 HTML 不一致:
| Server HTML | Client HTML |
|---|---|
<div>hello</div> |
<div>hello from client</div> |
➡ 直接导致:Hydration failed
🔥 所以重点:
✔ Client Component 依然会 SSR,因此不能在渲染逻辑里使用 window。
✔ 但可以在 useEffect 里使用,因为 effect 只在客户端执行。
推荐写法(100% 不会 Hydration failed)
如果你要用 window、document、localStorage 等:
tsx
"use client";
import { useEffect, useState } from "react";
export default function Page() {
const [text, setText] = useState("hello");
useEffect(() => {
setText("hello from client");
}, []);
return <div>{text}</div>;
}
✔ SSR 输出:hello
✔ 客户端 hydration 输出:hello(一致)
✔ hydration 后 useEffect 修改内容 → 不会报错
那什么时候可以用 typeof window !== 'undefined'?
✔ 只能在 effect / event handler 里用
tsx
useEffect(() => {
if (typeof window !== 'undefined') {
console.log(window.innerWidth);
}
}, []);
❌ 不能用于:
- 顶层逻辑
- render 逻辑(JSX 里)
useState初始值函数- 影响 SSR 输出的地方
如果真的要完全避免 SSR?
你需要 动态关闭 SSR ,而不是仅仅 use client:
tsx
const Page = dynamic(() => import("./page-client"), {
ssr: false
});
这样组件 完全在客户端渲染,不再进行 hydration 匹配。
总结(重要)
| 误解 | 正确理解 |
|---|---|
use client = only client |
❌ 错 |
| Client Component 不会在服务器执行 | ❌ 会在服务器执行一次(SSR) |
| 能直接在 JSX 或顶层使用 window | ❌ 会导致 SSR/CSR 内容不一致 |
需要浏览器 API → 用 useEffect |
✔ 正确做法 |