金数据技术博客 · №18
一个 Button,440 KB —— Server Component 里 import antd 的代价
TL;DR
- 在 Next.js 的 Server Component 里直接
import { X } from 'antd',会把整个 antd(全部 76 个组件目录)打进这个路由的 JS bundle - 即便配置了
experimental.optimizePackageImports也无效 - 修复方式:把
import { X } from 'antd'挪进 Client Component
背景
金数据的新版前端是一个体量不小的 Next.js 应用,系统内页面的 UI 组件库用的是 antd。最近在做 bundle size 优化时我们发现:有些路由明明只用了一两个 antd 组件,产物里却出现了完整的 antd。
排查下来,源头是一行看起来毫无问题的代码——在 Server Component 里 import 了 antd。
最小复现
创建两个 Next.js 项目(antd 6.2.0 / Next.js 16.2.6 / React 19.2.1),只渲染一个 Button 按钮。
A. Server Import
// app/page.tsx(Server Component)
import { Button } from 'antd'
export default function Page() {
return <Button type="primary">hello</Button>
}B. Client Import
// app/page.tsx(Server Component)
import ClientButton from './ClientButton'
export default function Page() {
return <ClientButton />
}
// app/ClientButton.tsx
'use client'
import { Button } from 'antd'
export default function ClientButton() {
return <Button type="primary">hello</Button>
}两个页面实际渲染出的 HTML 一字不差:
<button type="button" class="ant-btn css-var-root ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"><span>hello</span></button>但 JS bundle size 差别巨大:
| A. Server Import | B. Client Import | |
|---|---|---|
| JS Bundle (RAW) | 1458 KB | 176 KB |
| JS Bundle (gzip) | 440 KB | 56 KB |
| 打进产物的 antd 组件目录 | 76 个 | 12 个 |
开发环境的对比
Server Import:54 个请求,5486 KB,steps、table、transfer、upload…… 页面里根本不存在的组件全都被加载了。
Client Import:23 个请求,4446 KB(开发模式未压缩),没有多余的组件。
Production Bundle 分析对比
Server Import:All Route Modules 3.54 MB(压缩前),1545 个模块,包含所有 antd 组件。
Client Import:All Route Modules 1.05 MB(压缩前),365 个模块,仅包含 Button 和必须的 antd 依赖。
为什么会这样
antd/es/index.js 的第一行是:
"use client";所以 antd 的桶文件(Barrel)本身就是一个 client module。
Server Component 一旦 import 它,Next.js 就会在 antd/es/index.js 这个模块上建立 RSC client reference——整个 index.js 被当作一个 client 入口包裹进去,它 re-export 的 76 个组件全部成为可达代码。
optimizePackageImports 的原理是把桶文件 import 改写成对具体子路径的 import,但在这里改写无法生效,整个 barrel 都进了产物。
这个问题不只属于 antd:任何桶文件顶部带 "use client" 的组件库,在 Server Component 里整包 import 都会踩中同一个坑。
怎么修
把 antd 的使用挪进一个 Client Component,页面 / layout 保持 Server Component。渲染结果完全一致,bundle 里只剩下真正用到的组件。
在金数据的代码库里,我们把这条规则写进了架构约束:app/ 路由层禁止 import antd,antd 的配置(Provider、message / notification 等)统一放在 views 层的 client 组件里。
其他方式
直接 deep import 也可以绕过桶文件:
import Button from 'antd/es/button'但我们不推荐这个方式:antd/es/button 不是公开 API,且不是所有组件都有对应的路径。



