如何在项目中划分Server/Client边界

话题来源: React Server Components深度解析:为什么说它改变了前端游戏规则?

Server/Client 边界划分,本质不是“哪个组件放哪边”的语法题,而是一次架构分层决策。边界画错了,轻则多发几百 KB JavaScript,重则把数据库查询、鉴权状态、浏览器事件搅成一锅粥。一个可操作的判断标准是:数据越靠近源头,越适合 Server;交互越靠近用户,越适合 Client。

用三类问题定位边界

在项目里拆分组件时,不妨逐个模块问三个问题。

  • 它是否需要访问数据库、文件系统、内部 API、环境变量?
  • 它是否依赖 useStateuseEffect、点击事件、滚动监听、localStorage?
  • 它传给下游的数据,能否被 JSON 安全序列化?

只要答案落在第一类,优先放 Server。比如商品详情、文章正文、订单基础信息、权限菜单,这些内容通常来自后端系统,放在服务端可以减少接口暴露,也避免把无用逻辑打进浏览器。

只要答案落在第二类,基本就是 Client。筛选器、弹窗、表单校验、拖拽排序、图表缩放,都属于用户手边的交互,不该绕一圈服务器。

第三类经常被低估。Server 到 Client 传递的应该是字符串、数字、数组、普通对象;函数、数据库连接、类实例、复杂 Date 处理对象,都不该跨边界硬塞。

一个后台页面的拆法

以“订单管理页”为例,合理边界通常长这样:

  • Server:读取订单列表、校验当前用户角色、拼装表格初始数据
  • Server:渲染面包屑、标题、订单统计卡片
  • Client:搜索框输入状态、日期范围选择器、批量勾选、导出按钮交互
  • Client:表格列宽拖拽、弹窗确认、Toast 提示

某团队曾把整张订单表都做成 Client 组件,结果首屏 JS 接近 1.6MB,Chrome Performance 面板里主线程被脚本解析卡住近 900ms。后来只保留筛选条和操作列为 Client,表格主体改为 Server 输出,首屏包体降到 620KB。这个收益并不玄学,少发一半脚本,手机自然少喘两口气。

边界不要按文件名划,要按责任划

很多项目一开始会陷入误区:看到页面级组件就放 Server,看到小组件就放 Client。实际上,粒度不是关键,责任才是关键。

一个很小的头像组件,如果要读取用户会话并拼 CDN 地址,放 Server 合理;一个很大的看板组件,如果内部全是拖拽、缩放、实时刷新,那就应该成为 Client 岛屿。边界应该像手术刀,而不是油漆刷。

好的 Server/Client 边界,通常让服务端承担“准备事实”,让客户端承担“响应动作”。

实战中的几条硬规则

  • 不要为了省事在页面顶层写 'use client',这会把整棵子树推向浏览器
  • 第三方图表、富文本编辑器、地图 SDK,默认按 Client 处理
  • 鉴权、密钥、数据库访问永远不要进入 Client
  • Server 组件向 Client 组件传参时,先想象它会被 JSON.stringify
  • 可缓存、可复用、少交互的区域,优先留在 Server

真正麻烦的地方在中间地带:比如“带筛选的商品列表”。更稳的做法是让筛选控件在 Client,筛选结果由 URL 参数驱动 Server 重新渲染。这样既保留交互手感,又不会把整套数据查询逻辑搬到浏览器。边界画在 URL 上,有时比画在组件树里更干净。

评论