金数据技术博客 · №19
One Button, 440 KB: The Cost of Importing antd in a Server Component
TL;DR
- Importing
{ X } from 'antd'directly in a Next.js Server Component pulls the entire antd library (all 76 component directories) into that route's JS bundle experimental.optimizePackageImportsdoes not help- The fix: move the
import { X } from 'antd'into a Client Component
Background
Jinshuju (金数据) is an online form builder SaaS. Our new frontend is a fairly large Next.js application, and we use antd as the UI library for internal-facing pages. While working on bundle size optimization, we noticed something odd: some routes that use only one or two antd components ended up shipping the complete antd library.
The culprit turned out to be a single line of perfectly innocent-looking code — an antd import inside a Server Component.
Minimal Reproduction
Two Next.js projects (antd 6.2.0 / Next.js 16.2.6 / React 19.2.1), each rendering nothing but a single 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>
}Both pages render byte-for-byte identical 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>But the JS bundles are wildly different:
| A. Server Import | B. Client Import | |
|---|---|---|
| JS Bundle (RAW) | 1458 KB | 176 KB |
| JS Bundle (gzip) | 440 KB | 56 KB |
| antd component dirs in output | 76 | 12 |
Dev Mode Comparison
Server import: 54 requests, 5486 KB. steps, table, transfer, upload… components that don't exist anywhere on the page all get loaded.
Client import: 23 requests, 4446 KB (uncompressed dev mode), no unnecessary components.
Production Bundle Analysis
Server import: All Route Modules 3.54 MB (uncompressed), 1545 modules, containing every antd component.
Client import: All Route Modules 1.05 MB (uncompressed), 365 modules, containing only Button and its required antd dependencies.
Why This Happens
The first line of antd/es/index.js is:
"use client";So antd's barrel file is itself a client module.
The moment a Server Component imports it, Next.js establishes an RSC client reference on antd/es/index.js — the entire barrel becomes a client entry point, and all 76 components it re-exports become reachable code.
optimizePackageImports works by rewriting barrel imports into imports of concrete subpaths, but that rewrite cannot take effect here, so the whole barrel ends up in the output.
This is not specific to antd: any component library whose barrel file starts with "use client" has the same trap when imported wholesale from a Server Component.
The Fix
Move antd usage into a Client Component and keep the page / layout as a Server Component. The rendered output is identical, and the bundle only contains the components you actually use.
In the Jinshuju codebase we turned this into an architectural rule: the app/ routing layer must not import antd; antd configuration (Provider, message / notification, etc.) lives in client components in the views layer.
Alternatives
Deep imports also bypass the barrel:
import Button from 'antd/es/button'But we don't recommend it: antd/es/button is not a public API, and not every component has a corresponding path.



