Bundle Analysis
Understanding and optimizing JavaScript payload size
Pro Tip
Large JavaScript bundles delay interactivity. Run ANALYZE=true pnpm build
to visualize bundle composition and identify optimization opportunities.
Bundle Analysis
Time-to-interactive (TTI) measures when users can interact with your application. Large bundles delay TTI because browsers must:
Download the JavaScript
Network transfer time increases linearly with bundle size. On 3G connections, each megabyte takes several seconds.
Parse the code
JavaScript engines parse code before execution. Larger files consume more CPU time during parsing.
Execute and hydrate
React hydration matches server-rendered HTML with client-side JavaScript. Heavy bundles slow this critical process.
Running Bundle Analysis
The framework integrates @next/bundle-analyzer
in both apps:
ANALYZE=true pnpm build
This command builds your application and opens an interactive visualization showing:
Package Distribution
Treemap blocks represent JavaScript modules. Larger blocks indicate heavier dependencies.
Import Paths
Colors distinguish packages. Clicking zooms into specific modules for detailed inspection.
Size Metrics
Hover over blocks to see parsed size, gzipped size, and percentage of total bundle.
Analyze production builds
Development builds include debugging tools and hot reload infrastructure. Always analyze production builds for accurate measurements.
The configuration lives in next.config.ts
:
import withBundleAnalyzer from "@next/bundle-analyzer";
const bundleAnalyzerConfig =
process.env.ANALYZE === "true"
? withBundleAnalyzer({ enabled: true })(nextConfig)
: nextConfig;
export default bundleAnalyzerConfig;
Package Import Optimization
Next.js 15 includes automatic optimization for large libraries. The framework enables this for common dependencies:
experimental: {
optimizePackageImports: ["lucide-react", ...INTERNAL_PACKAGES],
}
This configuration transforms imports like:
import { User, Settings, LogOut } from "lucide-react";
Into granular requests that include only the specific icons. Without optimization, the entire icon library ships to the client.
Verify optimization in analysis
Run bundle analysis before and after adding large dependencies. Some libraries require manual optimization through dynamic imports.
Dynamic Imports for Route Splitting
Heavy components should load on-demand rather than blocking initial page load. The framework uses dynamic imports for:
Tree Shaking with Barrel Imports
Modern bundlers eliminate unused exports through tree shaking. This works automatically for ES modules but requires careful import patterns.
// Direct imports enable tree shaking
import debounce from 'lodash-es/debounce';
import { Button } from '@workspace/ui/components/button';
// Next.js optimizes these automatically
import { User, Settings } from 'lucide-react';
Direct imports tell bundlers exactly which modules to include. Unused exports get eliminated during build.
// Barrel imports may include entire packages
import * as _ from 'lodash-es';
import * as UI from '@workspace/ui';
// Without optimizePackageImports, this ships all icons
import * as Icons from 'lucide-react';
Wildcard imports prevent tree shaking. Bundlers can't determine which exports you use.
Monorepo Package Optimization
The framework configures workspace packages for optimal bundling:
const INTERNAL_PACKAGES = ["@workspace/ui", "@workspace/security"];
transpilePackages: INTERNAL_PACKAGES,
experimental: {
optimizePackageImports: [...INTERNAL_PACKAGES],
}
This enables:
- Hot reloading for local packages without build steps
- Tree shaking for workspace dependencies
- Import optimization across the entire monorepo
Internal packages ship as TypeScript source. Next.js transpiles and optimizes them during the application build, enabling aggressive dead code elimination.
Identifying Bundle Bloat
Common Bundle Bloat Issues
Watch for these patterns in bundle analysis:
Continuous Monitoring
Bundle analysis should run regularly, not just during optimization efforts:
Add bundle size checks to CI
GitHub Actions can fail builds that exceed size thresholds. This prevents gradual regression.
Review analysis before major releases
Compare bundle composition across versions. New dependencies should justify their weight.
Monitor production bundles
Real-world bundle sizes differ from development. Production builds include optimizations development skips.
Set budget alerts
Configure bundle size budgets in next.config.ts
to fail builds that exceed thresholds. This enforces performance discipline.