Bundle Size Reduction skill for Codex

Find the kilobytes that matter and cut them. Codex builds a treemap from the project's actual bundler, ranks the largest modules in the initial chunk by gzip size, and fixes the cause behind each: a whole library imported for one helper, duplicate versions, barrel files that defeat tree shaking, needless polyfills or server code leaking to the client. Every change is measured before and after, and a size-limit check keeps the win.

  • Performance
  • $bundle-size-reduction
  • 135 lines
  • 2 files
  • ~1,650 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- bundle-size-reduction

Installs to ~/.agents/skills/bundle-size-reduction/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $bundle-size-reduction, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Measures and shrinks JavaScript bundles - generates stats with rollup-plugin-visualizer, webpack-bundle-analyzer, esbuild metafiles, source-map-explorer or the Next.js analyzer, then removes the bytes that matter - heavy or duplicated dependencies, barrel-file imports that defeat tree shaking, unnecessary polyfills and code that belongs in another chunk. Use when the main bundle is too big, TBT or LCP suffers from JS, a dependency upgrade bloated the build, or someone asks what is in the bundle and how to make it smaller.

Use it when

  • The initial JavaScript for a key route is large or growing
  • TBT or LCP is dominated by script download, parse or execution
  • You need to know which dependency or import pulled in a large module

Use something else when

  • The code is needed but not on first load and you need lazy loading patterns: use code-splitting
  • The weight comes from tag managers and vendor scripts: use third-party-scripts

How to check the result

Rebuild in production mode and compare gzip and parsed sizes of the target route's initial chunks, then smoke the affected feature in a production preview.

The mistake it prevents

Marking a package sideEffects false when it imports CSS for effect, which silently drops styles from the build.

~/.agents/skills/bundle-size-reduction/SKILL.md

--- name: bundle-size-reduction description: Measures and shrinks JavaScript bundles - generates stats with rollup-plugin-visualizer, webpack-bundle-analyzer, esbuild metafiles,… license: MIT ---

Done well means: you know which modules make up the bytes on the critical route (compressed, not raw), each removal is proven with a before/after build stat, the app still works, and a size check stops the bundle from growing back.

Measure the right thing

  • Compressed (gzip or brotli) transfer size drives download time; parsed size drives parse and compile cost on slow CPUs. Report both.
  • The initial JS of the most important route matters more than the total of all chunks.
  • Always measure production builds. Dev builds include HMR and are unminified.

Workflow

- [ ] 1. Read AGENTS.md and package.json: bundler, framework, package manager, browserslist
- [ ] 2. Production build and record baseline sizes per entry/route (gzip + parsed)
- [ ] 3. Generate a treemap and list the 10 largest modules in initial chunks
- [ ] 4. Classify each (table below) and pick the cheapest fix with the biggest win
- [ ] 5. One change at a time; rebuild; record delta; run tests and smoke the app
- [ ] 6. Add or tighten a size limit in CI

Get a treemap

BundlerCommand / setup
Vite (Rollup)rollup-plugin-visualizer in build.rollupOptions.plugins or plugins, template: 'treemap', gzipSize: true, brotliSize: true
webpacknpx webpack --profile --json > stats.json then npx webpack-bundle-analyzer stats.json
Next.js (webpack build)@next/bundle-analyzer, run ANALYZE=true next build
Next.js 16.1+ (Turbopack)npx next experimental-analyze (experimental, interactive)
esbuild--metafile=meta.json, then the esbuild bundle analyzer page or esbuild.analyzeMetafile
Any, with source mapsnpx source-map-explorer 'dist/assets/*.js'
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
  plugins: [visualizer({ filename: 'stats.html', template: 'treemap', gzipSize: true, brotliSize: true })],
});

Installing analyzers needs network; add them as dev dependencies with the project’s package manager, or ask before installing in a sandbox without network.

Classify and fix

Finding in the treemapFix
Whole library for one function (lodash, moment, icon packs)Import the specific path or switch: lodash-es per-function, date-fns or Intl/Temporal where available, per-icon imports
Same package twice at different versionsnpm ls <pkg> / pnpm why <pkg>; align versions, use overrides/resolutions only after checking compatibility
Barrel file (index.ts re-exporting everything) pulls in unused modulesImport from the file directly; mark the package "sideEffects": false if true; Next.js experimental.optimizePackageImports for libraries
CommonJS dependency (no tree shaking)Find an ESM build or alternative; CJS modules are included whole
Polyfills for browsers you do not supportFix browserslist; drop core-js full imports; Lighthouse legacy-javascript-insight lists them
Feature used on one route or after interaction (editor, charts, maps, PDF)Lazy-load it: $code-splitting
Large JSON, locale data, SVG strings inlined in JSFetch at runtime, load one locale, serve SVG as files
Server-only code in the client bundle (SDKs, validation schemas used only on server)Move behind a server boundary; in Next.js add import 'server-only' to catch it
Dev-only code shipped (console, debug panels, mocks)Guard with import.meta.env.DEV / process.env.NODE_ENV so minifiers drop it

Check a replacement’s real cost before switching: bundle it alone (esbuild --bundle --minify entry.js | gzip -c | wc -c) rather than trusting a README claim.

Tree shaking checklist

  • Package exposes ESM ("module" or "exports" with import condition).
  • No top-level side effects, or "sideEffects" lists the files that do (CSS imports!). Setting "sideEffects": false on a package that imports CSS for effect drops the CSS.
  • Named imports, not import * as lib used dynamically.
  • Babel or TS not converting ESM to CJS before the bundler ("module": "commonjs" in a frontend tsconfig, or @babel/preset-env with modules: 'commonjs').

Verify a change

# Compare gzip sizes of built JS before and after (run from repo root after each build)
find dist -name '*.js' -exec sh -c 'printf "%8d %s\n" "$(gzip -9c "$1" | wc -c)" "$1"' _ {} \; | sort -rn | head -20
  • Delta measured on initial JS for the target route, gzip and parsed.
  • Tests pass; the affected feature works in a production preview (vite preview, next start). Removing a “side effect” import can silently drop CSS or polyfills.
  • If the size did not drop, the module is still reachable: search the stats file for who imports it (webpack-bundle-analyzer and the Next analyzer show import reasons).

Keep it small

Add a limit that fails CI. Default to size-limit (.size-limit.json, plus the @size-limit/file preset as a dev dependency):

[
  { "name": "home initial JS", "path": "dist/assets/index-*.js", "limit": "120 kB", "gzip": true }
]
npx size-limit          # fails non-zero when over the limit

Set the limit a few percent above today’s size and lower it after each win. Cross-cutting budgets (Lighthouse, API latency) belong in $performance-budgets.

Gotchas

  • Treemaps of parsed size exaggerate highly compressible code (JSON, repeated strings); sort by gzip before deciding.
  • manualChunks that groups all of node_modules into one vendor chunk makes every route download every dependency. Split by usage, not by origin.
  • Replacing a library saves nothing if another dependency still imports it.
  • Upgrading a framework can double-ship two versions of React or a UI kit during the transition; check for duplicates after every major upgrade.
  • Minifier settings (drop_console, pure_funcs) only remove code without side effects; verify the output rather than assuming.

Report template

## Bundle size: <app> (<bundler>, production build)
| Entry / route | Before gzip | After gzip | Parsed after |
|---|---|---|---|
| home initial | 312 kB | 186 kB | 610 kB |

Changes:
1. Replaced moment with date-fns per-function imports: -71 kB gzip (src/utils/date.ts)
2. Lazy-loaded the chart editor on /reports: -48 kB from home initial
Guard: size-limit entry "home initial JS" at 195 kB
Not done: <duplicate react-is via library X, needs upstream fix>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Bundle Size Reduction"
  short_description: "Find the kilobytes that matter and cut them"
  default_prompt: "$bundle-size-reduction Our main.js is 900 kB, show me what is in it and cut it down"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem