Client Components
2026年9月6日
文件第一行写 'use client',这个模块以及它 import 的模块都会被当成客户端组件图的一部分。
"use client";
import { useEffect, useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
useEffect(() => {
console.log(window.innerWidth);
}, []);
return <button onClick={() => setN((x) => x + 1)}>{n}</button>;
}
tsx
仍会在服务器预渲染
客户端组件不是「只在浏览器跑」。服务器会先画出一版静态 HTML(上面例子里能看到 0 和按钮),再在浏览器水合。所以:
- 模块顶层、函数体里的
console.log开发时可能打两次(一次服务器、一次客户端) window/document必须放进useEffect或事件回调,否则服务端预渲染会炸
嵌套方向
服务端 → 客户端可以。
客户端 → 服务端不行:客户端 bundler 会把子树都当成客户端模块,里面的 fs、密钥立刻出问题。
需要「客户端壳 + 服务端内容」时,让服务端当父亲,把客户端当孩子;或者把服务端结果通过 children 插进客户端:
// ServerParent.tsx(服务端)
import ClientTabs from "./ClientTabs";
import ServerList from "./ServerList";
export default function ServerParent() {
return (
<ClientTabs>
<ServerList />
</ClientTabs>
);
}
tsx
ClientTabs 只负责切换 UI,不 import ServerList。
server-only
fetch、WebSocket 两边都能用,有人会写出「服务端客户端都能 import」的工具函数,一不小心把只该在服务器跑的逻辑带进 bundle。
pnpm add server-only
bash
import "server-only";
export function loadSecretConfig() {
return { key: process.env.PRIVATE_KEY };
}
ts
客户端一旦 import 这个文件,构建会失败。反过来有 client-only,给只该在浏览器跑的模块用。
什么时候标 use client
- 有 hooks、有事件、要读浏览器 API
- 用了只能在客户端跑的库(部分图表、编辑器)
能留在服务端的就留下。'use client' 是边界,不是「这个文件比较重要」的标记。标得越高,进包的代码越多。
下一篇:Next 16 的 Cache Components,静态壳和动态洞怎么编在一起。