How to Reduce Bundle Size with an AI Coding Agent
A real before/after bundle-analysis workflow showing what happens when an AI coding agent acts on actual analyzer data instead of generic bundle-size advice.
To reduce bundle size with an AI coding agent, don't ask it to "make the bundle smaller." Run a bundle analyzer first, such as webpack-bundle-analyzer or Vite's rollup-plugin-visualizer, and hand the agent the actual output: which chunks are largest, which packages appear more than once, and what's landing in the initial load versus a lazy chunk. An agent working from real numbers finds the barrel import pulling in an entire icon library or the date library that's three times bigger than it needs to be. An agent working from a vague request reaches for the same cosmetic move every time: wrap a component in a dynamic import and call it done, without checking whether that actually changed what loads on first paint.
Why Generic Prompts Produce Cosmetic Fixes
"Reduce my bundle size" gives the agent no data to reason from, so it falls back on the moves that are true in general but not necessarily true for your bundle: lazy-load routes, add React.lazy around big components, remove unused dependencies it can find by scanning imports. These aren't wrong exactly, they're just untargeted. A codebase can end up with a dozen dynamic imports and barely move the number that matters, its initial JavaScript payload, because the biggest chunk was never one of the components anyone thought to split. Bundle size problems are usually concentrated in a small number of specific causes, and finding them requires looking at actual output, not pattern-matching against common advice.
Give the Agent Real Data First
Generate a stats file or a visual report before you prompt anything. For webpack, run the build with --profile --json and feed it to webpack-bundle-analyzer, or use webpack's own code-splitting guide as a reference for what a healthy chunk graph looks like. For Vite, add rollup-plugin-visualizer to the build config and it will produce a treemap after vite build runs. Either way, you want three things in front of the agent: a list of the largest chunks by size, which packages show up in more than one chunk, and which chunks are actually requested on first load versus fetched later. Paste the summary, or the relevant slice of stats.json, and ask the agent to read it before proposing anything, the same discipline that matters for debugging a stack trace rather than fixing based on the error text alone.
Three Tradeoffs Agents Get Wrong Without Real Data
Barrel-File Imports
A barrel file, an index.ts that re-exports everything from a folder, is convenient to import from but murder on tree shaking. import { Button } from '@/components' looks harmless, but if that barrel re-exports fifty components and the bundler can't prove the other forty-nine are unused, all fifty end up in the chunk. Agents frequently miss this because the import statement itself looks clean and idiomatic. They'll see a large chunk, correctly identify that a UI library is contributing most of its size, and then reach for lazy-loading the page instead of importing directly from the specific component file and letting the barrel go unused for that import path. The fix is usually a one-line import change, not a new Suspense boundary.
Over-Eager Library Choices
Agents tend to reach for the library they've seen most often in training data, not the smallest one that does the job. Moment.js versus day.js is the classic case: both format dates, but moment ships its entire locale set by default and doesn't tree-shake, while day.js is a fraction of the size for the same common operations. Lodash versus lodash-es is similar, importing the whole lodash package pulls in the full utility library even if you use three functions, while lodash-es or targeted imports like lodash/debounce let a bundler drop the rest. An agent asked to "fix the bundle" without being shown that a specific dependency is 40% of a chunk will rarely think to question the dependency itself. Shown the analyzer output naming that package by size, it usually will.
Cosmetic Code-Splitting
Wrapping a component in React.lazy or a dynamic import() only helps if that component wasn't already going to load on the current page. Agents sometimes split components that are always rendered on the main route, which adds a network waterfall and a loading flicker without removing a single byte from what the user's browser has to fetch for that page. Real code-splitting follows your actual navigation graph: a settings page that's rarely visited is a good split target, a header that renders on every route is not. This is a structural decision about what users actually load on which paths, and it requires knowing your app's routes, not just its component tree, which is exactly the kind of thing an agent gets wrong when it's optimizing for "looks like best practice" instead of your specific traffic pattern.
A Worked Before/After Example
Say your analyzer output shows a 340KB vendor chunk, with a treemap listing moment.js at 65KB, an icon library at 90KB imported through a barrel file, and a charting library at 110KB that's only used on one rarely-visited analytics page.
The Superficial Fix
Asked to just "reduce bundle size," an agent without the analyzer data will often add React.lazy around two or three page-level components chosen somewhat arbitrarily, maybe remove a couple of genuinely unused npm packages found by scanning package.json against imports, and call the job done. The 340KB vendor chunk barely moves, because none of the three real contributors got touched.
The Correct Fix
Given the actual treemap, a good prompt looks like: "Here is the bundle analyzer output. The vendor chunk is 340KB. moment.js is 65KB of it, this icon library is 90KB and imported through src/components/icons/index.ts, and this charting library is 110KB and only used in src/pages/Analytics.tsx. Replace moment.js with day.js everywhere it's used for formatting, change icon imports to import directly from individual icon files instead of the barrel, and move the charting library import into a dynamic import scoped to the analytics route only. Show me the resulting chunk sizes after each change." That produces three targeted, verifiable changes instead of a scattershot of Suspense boundaries, and it gives you a before and after number for each one so you can confirm the fix actually worked rather than assuming it did.
A Prompt Template for Bundle Work
Reuse this structure each time: generate fresh analyzer output, paste the largest chunks and any package appearing in more than one chunk, name the specific files where the heaviest imports originate, and ask for changes scoped to those exact files with a re-measurement step after each change. This keeps the work grounded in what your build actually produces instead of general JavaScript performance advice, which matters whichever AI coding tool you're running it through. It also tends to surface the same kind of root-cause thinking that matters when an agent is deciding which parts of your codebase to touch first: specific, bounded changes it can verify beat broad changes it can only assume worked.
Tree shaking specifically depends on your imports being statically analyzable, which webpack's own tree shaking guide covers in more depth, including the sideEffects flag in package.json that tells the bundler which files are safe to drop if unused. It's worth pointing an agent at that concept explicitly if you keep seeing packages that should be tree-shakeable show up whole in a chunk. And however much context you hand the agent for this kind of work, how much context you give it matters more than how much you ask it to do in one pass. A narrow, well-evidenced request against real analyzer data outperforms a broad one every time.
What's the fastest way to see what's actually in my JavaScript bundle?
Run webpack-bundle-analyzer against a production build, or add rollup-plugin-visualizer to a Vite config. Both produce a treemap you can screenshot or export as data and hand directly to an agent instead of describing the problem from memory.
Will an AI coding agent find unused dependencies on its own?
It can scan package.json against your imports and flag packages with no references, which catches genuinely dead dependencies. It won't catch a dependency that's used but oversized for the job, like moment.js used only for basic formatting, unless you show it the size data that makes that case obvious.
Does code-splitting always reduce the size users download?
No. It changes when code loads, not how much exists overall, and if you split something that loads on every page anyway, you've added a network request without removing any bytes from the critical path. Splitting only helps when the split-out code genuinely isn't needed on the current page.
How often should I re-run the bundle analyzer when an AI agent is making changes?
After every change that's supposed to affect size, not just at the end. Asking for a before and after number on each step is what turns a plausible-sounding fix into a verified one.
How did this land?
About the author

Staff Engineer, Platform
Carlo works on the platform that turns prompts into running apps. He writes the engineering deep dives and the changelog notes worth reading.


