Bundle size management and optimization with performance budgets
Recommended bundle size limits by connection and device type
Initial HTML + CSS + JS
Main + vendor chunks
Critical + deferred
Based on 5s load time on 3G connection (1.6Mbps)
Initial load budget
Main application code
Styles + frameworks
Optimized for sub-3s load time on fast connections
Tools and techniques to analyze and optimize bundle sizes
// Webpack Bundle Analyzer
npm install --save-dev webpack-bundle-analyzer
// package.json script
"analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"
// Next.js bundle analysis
npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({
// Next.js config
})
// Run analysis
ANALYZE=true npm run build
// Source Map Explorer
npm install --save-dev source-map-explorer
npx source-map-explorer build/static/js/*.js// bundlesize package
npm install --save-dev bundlesize
// package.json
{
"bundlesize": [
{
"path": "./build/static/js/main.*.js",
"maxSize": "130kb"
},
{
"path": "./build/static/css/*.css",
"maxSize": "40kb"
}
],
"scripts": {
"bundlesize": "bundlesize"
}
}
// CI Integration
npm run build && npm run bundlesize
// Bundlemon for tracking over time
npm install --save-dev bundlemon
npx bundlemon --subProject "main-app"Effective patterns for breaking large bundles into manageable chunks
// Route-based splitting (Next.js automatic)
// pages/dashboard.js → dashboard.chunk.js
// pages/profile.js → profile.chunk.js
// Component-based splitting
const HeavyChart = lazy(() => import('./HeavyChart'))
const AdminPanel = lazy(() => import('./AdminPanel'))
// Vendor splitting (Webpack)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
minChunks: 2,
chunks: 'all',
name: 'common'
}
}
}
}
}
// Feature-based splitting
const FeatureA = lazy(() => import('./features/FeatureA'))
const FeatureB = lazy(() => import('./features/FeatureB'))
// Library splitting for better caching
const ReactChunk = lazy(() => import('./chunks/ReactChunk'))
const UtilsChunk = lazy(() => import('./chunks/UtilsChunk'))Eliminating dead code to reduce bundle size
// ❌ Imports entire library
import _ from 'lodash'
import * as utils from './utils'
// ✅ Import only what you need
import { debounce, throttle } from 'lodash'
import { formatDate } from './utils'
// ✅ Library-specific optimized imports
import debounce from 'lodash/debounce'
import Button from '@mui/material/Button'
// ✅ Conditional imports
const HeavyModule = await import('./HeavyModule')
if (condition) {
const { helper } = await import('./helpers')
}
// babel-plugin-import for automatic optimization
{
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}]
]
}// Webpack production config
module.exports = {
mode: 'production', // Enables tree shaking
optimization: {
usedExports: true,
sideEffects: false, // Safe if no side effects
minimize: true
}
}
// package.json sideEffects declaration
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}
// ESLint dead code detection
"rules": {
"no-unused-vars": "error",
"no-unreachable": "error"
}
// Remove console.logs in production
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
]
}Serving optimized bundles to modern browsers
// Next.js modern bundle (automatic)
// Generates both modern and legacy bundles
// Vite modern build
export default {
build: {
target: ['es2015', 'chrome58', 'firefox57', 'safari11'],
modernPolyfills: true
}
}
// Manual differential serving
<script type="module" src="modern-bundle.js"></script>
<script nomodule src="legacy-bundle.js"></script>
// Babel preset-env for modern browsers
{
"presets": [
["@babel/preset-env", {
"targets": {
"esmodules": true
},
"bugfixes": true,
"shippedProposals": true
}]
]
}
// Webpack 5 automatic target detection
module.exports = {
target: ['web', 'es2017'],
experiments: {
outputModule: true
}
}
// Size comparison
Legacy bundle: 450KB
Modern bundle: 320KB (-29%)
Savings: No polyfills, native syntaxContinuous monitoring and alerting for bundle size regression
# GitHub Actions
name: Bundle Size Check
on: [pull_request]
jobs:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run build
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# size-limit config
module.exports = [
{
path: 'build/static/js/*.js',
limit: '130 KB'
},
{
path: 'build/static/css/*.css',
limit: '40 KB'
}
]Advanced techniques to minimize bundle sizes