Build Optimization
When to activate
Slow builds, large bundle sizes, CI pipelines taking more than 5 minutes, or when the user mentions Webpack, Vite, Turbo, esbuild, or Rollup performance issues.
When NOT to use
- Runtime performance problems (CPU, memory, network) — this skill targets compile/bundle time only
- First-time project setup where no baseline exists
- Projects using bundlers other than the ones listed above (e.g., Parcel, Brunch)
Instructions
Always start with analysis, never with changes.
Bundle Analysis (run first)
- Webpack:
npx webpack-bundle-analyzer stats.json — generate stats with webpack --profile --json > stats.json
- Vite:
npx vite-bundle-visualizer after adding { build: { reportCompressedSize: true } }
- Identify: largest chunks, duplicated dependencies, unexpectedly included modules
Code Splitting
- Dynamic imports:
const Foo = () => import('./Foo') for route-level and feature-level splits
- Route-based splitting in React Router / Next.js:
React.lazy + Suspense
- Vendor chunk isolation: separate rarely-changing third-party code from app code
- Vite
manualChunks:build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-tooltip'],
},
},
},
}
- Webpack
SplitChunksPlugin:optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' },
},
},
}
Tree Shaking
- Requires ES module syntax (
import/export) throughout — CommonJS (require) disables tree shaking
- Add
"sideEffects": false to package.json (or list CSS files that have side effects)
- Audit barrel files (
index.ts) — re-exporting everything defeats tree shaking; use direct imports
TypeScript Incremental Compilation
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
}
}
Add .tsbuildinfo to .gitignore, cache it in CI keyed on source hash.
Turborepo Task Caching
{
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**"],
"inputs": ["src/**", "package.json", "tsconfig.json"]
}
}
}
Remote caching: npx turbo link — shares cache hits across team members and CI.
CI Cache Strategy (GitHub Actions pattern)
- uses: actions/cache@v3
with:
path: node_modules
key: node-${{ hashFiles('**/package-lock.json') }}
- uses: actions/cache@v3
with:
path: dist
key: build-${{ hashFiles('src/**') }}
Target: >90% cache hit rate. A miss on node_modules should not invalidate the build output cache.
Common Quick Wins (no architectural change required)
- Add
.dockerignore mirroring .gitignore — prevents sending node_modules into build context
- Enable
vite.optimizeDeps.include for large CJS deps that Vite pre-bundles slowly
- Replace
ts-node with tsx for scripts — tsx uses esbuild and is ~10× faster for one-off execution
- Switch
jest to vitest for TypeScript projects — eliminates Babel transform overhead
- Enable
esbuild as the TypeScript transformer in Webpack via esbuild-loader
Example
Symptom: CI build takes 8 minutes, bundle is 4 MB gzipped.
Steps taken:
- Run
npx vite-bundle-visualizer — reveals moment.js (300 KB) and all locales included
- Replace
moment with date-fns tree-shaken imports — saves 280 KB
- Add
manualChunks to split vendor from app code — reduces first-load chunk from 1.2 MB to 380 KB
- Add Turborepo with
outputs: ["dist/**"] — second CI run hits cache, build time drops to 45 seconds
1---2name: build-optimization3description: Slow builds, large bundle sizes, CI pipelines taking more than 5 minutes, or when the user mentions Webpack, Vite, Turbo, esbuild, or Rollup perfor...4---56# Build Optimization78## When to activate9Slow builds, large bundle sizes, CI pipelines taking more than 5 minutes, or when the user mentions Webpack, Vite, Turbo, esbuild, or Rollup performance issues.1011## When NOT to use12- Runtime performance problems (CPU, memory, network) — this skill targets compile/bundle time only13- First-time project setup where no baseline exists14- Projects using bundlers other than the ones listed above (e.g., Parcel, Brunch)1516## Instructions1718**Always start with analysis, never with changes.**1920### Bundle Analysis (run first)21- Webpack: `npx webpack-bundle-analyzer stats.json` — generate stats with `webpack --profile --json > stats.json`22- Vite: `npx vite-bundle-visualizer` after adding `{ build: { reportCompressedSize: true } }`23- Identify: largest chunks, duplicated dependencies, unexpectedly included modules2425### Code Splitting26- Dynamic imports: `const Foo = () => import('./Foo')` for route-level and feature-level splits27- Route-based splitting in React Router / Next.js: `React.lazy` + `Suspense`28- Vendor chunk isolation: separate rarely-changing third-party code from app code29- Vite `manualChunks`:30 ```js31 build: {32 rollupOptions: {33 output: {34 manualChunks: {35 vendor: ['react', 'react-dom'],36 ui: ['@radix-ui/react-dialog', '@radix-ui/react-tooltip'],37 },38 },39 },40 }41 ```42- Webpack `SplitChunksPlugin`:43 ```js44 optimization: {45 splitChunks: {46 chunks: 'all',47 cacheGroups: {48 vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' },49 },50 },51 }52 ```5354### Tree Shaking55- Requires ES module syntax (`import`/`export`) throughout — CommonJS (`require`) disables tree shaking56- Add `"sideEffects": false` to `package.json` (or list CSS files that have side effects)57- Audit barrel files (`index.ts`) — re-exporting everything defeats tree shaking; use direct imports5859### TypeScript Incremental Compilation60```json61{62 "compilerOptions": {63 "incremental": true,64 "tsBuildInfoFile": ".tsbuildinfo"65 }66}67```68Add `.tsbuildinfo` to `.gitignore`, cache it in CI keyed on source hash.6970### Turborepo Task Caching71```json72{73 "pipeline": {74 "build": {75 "outputs": ["dist/**", ".next/**"],76 "inputs": ["src/**", "package.json", "tsconfig.json"]77 }78 }79}80```81Remote caching: `npx turbo link` — shares cache hits across team members and CI.8283### CI Cache Strategy (GitHub Actions pattern)84```yaml85- uses: actions/cache@v386 with:87 path: node_modules88 key: node-${{ hashFiles('**/package-lock.json') }}8990- uses: actions/cache@v391 with:92 path: dist93 key: build-${{ hashFiles('src/**') }}94```95Target: >90% cache hit rate. A miss on node_modules should not invalidate the build output cache.9697### Common Quick Wins (no architectural change required)981. Add `.dockerignore` mirroring `.gitignore` — prevents sending `node_modules` into build context992. Enable `vite.optimizeDeps.include` for large CJS deps that Vite pre-bundles slowly1003. Replace `ts-node` with `tsx` for scripts — tsx uses esbuild and is ~10× faster for one-off execution1014. Switch `jest` to `vitest` for TypeScript projects — eliminates Babel transform overhead1025. Enable `esbuild` as the TypeScript transformer in Webpack via `esbuild-loader`103104## Example105106**Symptom:** CI build takes 8 minutes, bundle is 4 MB gzipped.107108**Steps taken:**1091. Run `npx vite-bundle-visualizer` — reveals `moment.js` (300 KB) and all locales included1102. Replace `moment` with `date-fns` tree-shaken imports — saves 280 KB1113. Add `manualChunks` to split vendor from app code — reduces first-load chunk from 1.2 MB to 380 KB1124. Add Turborepo with `outputs: ["dist/**"]` — second CI run hits cache, build time drops to 45 seconds113114---