【Next】客户端组件和服务端组件

概述

在 Next.js(尤其是 App Router 体系)里,客户端组件(Client Component)和服务端组件(Server Component) 是基于 React 的 RSC(React Server Components)模型实现的。

  • 服务端组件(Server Component)默认执行在服务端(Node.js),用于数据获取、减少 bundle
  • 客户端组件(Client Component)运行在浏览器,用于交互、状态、事件
  • 二者通过 序列化协议(Flight) 进行通信
能力 Server Client
访问数据库
使用 useState
使用 useEffect
绑定事件 onClick
减少 bundle 体积
tsx 复制代码
// 默认就是 Server Component
export default function Page() {
  return <div>Hello</div>;
}
tsx 复制代码
// 显式声明 Client Component
"use client";

export default function Button() {
  return <button onClick={() => alert(1)}>Click</button>;
}

"use client" 是一个 编译时指令(不是运行时)

Server Component 深入

为什么默认是 Server?

核心目标:减少前端 bundle + 提升首屏性能

tsx 复制代码
// 直接在服务端查数据
async function Page() {
  const data = await fetch("https://api.com/user");
  return <div>{data.name}</div>;
}
  • ❌ 不需要把 fetch 逻辑打包到浏览器
  • ❌ 不需要 useEffect + loading
  • ✅ SSR 天然支持

可以直接访问后端资源

ts 复制代码
import db from "@/lib/db";

export default async function Page() {
  const users = await db.user.findMany();
  return <div>{users.length}</div>;
}
  • Prisma
  • 内部 RPC(BFF)
  • GraphQL

不支持交互

tsx 复制代码
// ❌ 错误
export default function Page() {
  return <button onClick={() => {}}>Click</button>;
}
  • Server Component 不会在浏览器执行
  • 没有事件系统

Client Component 深入

只要涉及以下任意一点,就需要使用客户端组件:

  • useState
  • useEffect
  • DOM 操作
  • 事件(onClick)
  • 浏览器 API(localStorage)
tsx 复制代码
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}
  • 会被打进 JS bundle
  • 会增加 hydration 成本
  • 会影响 TTI

所以 能不用 client 就不用

核心机制

Server → Client 的传输原理(Flight)

Server Component 渲染后不会直接输出 HTML,而是:

输出一种特殊 JSON(React Flight)

示意:

json 复制代码
[
  ["div", null, "Hello"],
  ["$L1", "Button"]
]

客户端再:

  • 解析结构
  • 加载对应 Client Component
  • Hydration

限制:只能传"可序列化数据"

tsx 复制代码
// ❌ 错误
<ClientComp fn={() => {}} />
tsx 复制代码
// ✅ 正确
<ClientComp count={1} />

因为需要跨网络传输

组合模式

推荐结构:Server + Client 嵌套

tsx 复制代码
// Server Component
import Button from "./Button";

export default async function Page() {
  const data = await fetchData();

  return (
    <div>
      <h1>{data.title}</h1>
      <Button />
    </div>
  );
}
tsx 复制代码
// Client Component
"use client";

export default function Button() {
  return <button>Click</button>;
}

注意: Client 不能 import Server

tsx 复制代码
// ❌ 错误
"use client";
import ServerComp from "./ServerComp";
tsx 复制代码
// ✅ Server 可以 import Client
相关推荐
快乐肚皮36 分钟前
深入理解Loop Engineering
前端·后端
半个落月39 分钟前
从递归到快速排序:用 JavaScript 把分治思想讲明白
javascript·算法·面试
风骏时光牛马1 小时前
VHDL十大经典基础功能设计实例代码合集
前端
小兔崽子去哪了1 小时前
Vue3 + Pinia 集成 IGV.js 实现 BAM 文件在线浏览
javascript·vue.js·后端
hunterandroid1 小时前
Notification 通知:从基础到渠道适配
前端
孟陬1 小时前
Claude Code 巧思 `Ctrl+S` 暂存键
前端·后端
PedroQue991 小时前
V1.6.1性能优化:高频路径提速与代码精简
前端·uni-app
猩猩程序员2 小时前
将 LiteLLM 迁移到 Rust —— 构建最快、最轻量的 AI Gateway
前端
小月土星2 小时前
JavaScript 快速排序:从 pivot、双指针到分治思想
javascript·算法·面试
lichenyang4532 小时前
JSBridge 分发升级:为什么要从 if-else 变成 Registry > 这是「ASCF 架构升级」系列的第 3 篇
前端