Skip to content

Bundle Optimization

The JavaScript bundle is the code the browser must download, parse, and execute before a page becomes interactive. Optimizing the bundle — making it smaller and loading only what’s needed — is one of the most impactful performance improvements you can make.

A 500KB JavaScript bundle takes significantly longer to parse and execute than a 150KB one — especially on mobile devices with slower CPUs. Next.js automatically splits code by route, but you still need to be mindful of what you add to each page.

Next.js automatically creates a separate bundle for each route:

- / → chunk-home.js (40KB)
- /about → chunk-about.js (25KB)
- /blog → chunk-blog.js (60KB)

When the user visits /, only chunk-home.js is downloaded. The other bundles load on demand.

Next.js automatically removes unused exports from your bundles. This happens at build time when you import specific functions from a library:

// ❌ Imports the entire lodash library (~70KB)
import _ from 'lodash'
_.debounce(fn, 300)
// ✅ Only imports the debounce function (~5KB)
import debounce from 'lodash/debounce'
debounce(fn, 300)

Use @next/bundle-analyzer to visualize what’s in your bundles:

Terminal window
npm install @next/bundle-analyzer
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// your config
})
Terminal window
ANALYZE=true npm run build

This opens an interactive treemap showing the size of every imported module. Use it to identify large, unnecessary dependencies.

LibraryApprox SizeOptimization
Moment.js230KBUse date-fns (tree-shakeable)
Lodash70KBImport individual functions
Chart.js60KBDynamic import on interaction
Three.js500KB+Dynamic import, only when needed
// ❌ Moment.js — can't tree-shake
import moment from 'moment'
moment().format('YYYY-MM-DD')
// ✅ date-fns — tree-shakeable
import { format } from 'date-fns'
format(new Date(), 'yyyy-MM-dd')

2. Use Dynamic Imports for Large Libraries

Section titled “2. Use Dynamic Imports for Large Libraries”
const Chart = dynamic(() => import('recharts').then(m => m.LineChart), {
ssr: false,
})

Regularly audit your package.json for unused packages:

Terminal window
npm prune
npx depcheck
  • Not analyzing the bundle — You can’t optimize what you don’t measure. Run the bundle analyzer before optimizing.
  • Importing entire libraries for one function — Always import specific functions, not the whole library.
  • Not lazy loading heavy components — Charts, maps, and editors should be dynamic imports.
  • Including polyfills unnecessarily — Next.js automatically includes modern polyfills. Only add what’s needed for your target browsers.
  • Keep the initial bundle under 200KB (gzipped)
  • Use dynamic imports for components > 10KB
  • Prefer tree-shakeable libraries (date-fns, lodash-es)
  • Run bundle analyzer regularly to catch regressions
  • Remove unused dependencies from package.json

Bundle optimization is about shipping only the JavaScript that’s needed. Next.js handles route-level code splitting automatically. Use tree-shakeable imports, dynamic imports for heavy components, and the bundle analyzer to identify optimization opportunities.