First, look at a map of the bundle. Then fix the biggest pieces first.
Finding out what is in it:
- Run a bundle analyzer, like
webpack-bundle-analyzer,rollup-plugin-visualizer(Vite) or@next/bundle-analyzer. It draws a treemap: each box is a module, sized by how many bytes it adds. - Use
source-map-exploreron the built files if you have source maps. - Open the Chrome DevTools Coverage tab. It shows how much of each file never ran during page load.
- Check sizes before you install a package, for example on bundlephobia.
What you usually find, and the fix:
- One huge library for one small feature (a date library with every locale, a chart library). Import only what you use, or switch to a smaller library or a built-in API like
Intl. - The same library twice, in two versions. Deduplicate it in the lockfile.
- Code only some users need (an admin page, a rich text editor, a PDF export). Split it out with dynamic
import()so it loads on demand. - Imports that can't be tree-shaken, such as CommonJS packages or barrel files. Import from the exact file, or use the library's ES module build.
- Polyfills for old browsers that your users don't have. Update the browser targets.
After the fix, add a size limit in CI so the bundle doesn't grow back.
- A bundle analyzer shows each module as a box sized by its bytes.
- The DevTools Coverage tab shows code that downloads but never runs.
- Big libraries used for one small feature are a common cause.
- Code only some users need should move into a chunk loaded with dynamic
import(). - Duplicate versions, CommonJS packages and extra polyfills also add size.
- A size limit in CI stops the bundle from growing back.