无废话系列之 use client ≠ client only

今儿部门新人问我:"已经在页面里写明了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 ✔ 正确做法
相关推荐
Csvn4 小时前
WASM
前端
修己xj5 小时前
我用 AI 给自己写了一个 Markdown 编辑器,然后把它开源了
前端
FRing036 小时前
vue2干货总结2(v-bind、v-for、v-model、指令修饰符、v-bind操作class和style)
开发语言·前端·vue.js·vue2
幸运小猿6 小时前
模拟慢上传-处理超时重试image 丢失线上问题
前端·javascript·html
moshuying7 小时前
网购能买Token了!
前端
h_65432108 小时前
echarts实现柱状图加一条横线
前端·echarts
风骏时光牛马8 小时前
生成一个后端标题,只需要一个标题,不需要其他的任何文本。
前端
子非鱼@Itfuture8 小时前
关于前后端联调中登录状态/登录信息失效?
java·前端
郭小李8 小时前
openFeign入门
java·开发语言·前端
馒头不想说话8 小时前
AI当运营,帮你省人力
前端