JS Bundle Size Optimization
Tree-shaking, code-splitting, dynamic imports, bundle analysis, dependency audit.
performancefrontendwebpackvite
# JS Bundle Size Optimization
## Analyze first
```bash
# Next.js
NEXT_ANALYZE=true next build # needs @next/bundle-analyzer
# Vite
npx vite-bundle-visualizer
# Webpack
npx webpack-bundle-analyzer stats.json
```
## Tree-shaking
```js
// BAD — imports entire library
import _ from 'lodash' // ~70KB gzip
// GOOD — cherry-pick
import debounce from 'lodash/debounce' // ~1KB
// BEST — use native
const debounce = (fn, ms) => {
let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms) }
}
```
Ensure `"sideEffects": false` in package.json for your own code.
## Code splitting
```tsx
// Route-based (automatic in Next.js)
const Admin = lazy(() => import('./pages/Admin'))
// Feature-based
const PDFViewer = lazy(() => import('./PDFViewer'))
<button onClick={() => import('./heavy-lib').then(m => m.run())}>
```
## Replace heavy deps
| Heavy | Lighter alternative | Savings |
|-----------------|------------------------|-----------|
| moment | dayjs / date-fns | ~60KB |
| lodash (full) | lodash-es (tree-shake) | ~50KB |
| axios | native fetch / ky | ~12KB |
| uuid | crypto.randomUUID() | ~3KB |
| classnames | clsx | ~1KB |
## Dynamic imports for non-critical
```tsx
// Load syntax highlighter only when code block is visible
useEffect(() => {
if (hasCodeBlock) {
import('prismjs').then(Prism => Prism.highlightAll())
}
}, [hasCodeBlock])
```
## Build config
```js
// next.config.js
module.exports = {
compiler: { removeConsole: { exclude: ['error'] } },
experimental: { optimizePackageImports: ['lucide-react', '@icons-pack/react-simple-icons'] },
}
```
## Target
- First Load JS < 100KB (gzipped) for most pages.
- Largest chunk < 200KB.
- Use `npx bundlephobia <pkg>` to check before adding deps.
API: /api/skills/bundle-size-optimization