Webpack Chunks — Configuring Webpack for Optimal Code Splitting
In this tutorial, you will learn about Webpack Chunks. We cover key concepts, practical examples, and best practices to help you master this topic.
Webpack chunk configuration controls how code is split into separate files, balancing chunk count, size, and Caching for optimal loading performance.
What You'll Learn
By the end of this tutorial, you'll understand Webpack chunk configuration, how to configure splitChunks, how to create vendor and shared chunks, how to name chunks, and how to analyze bundle output.
Why It Matters
Webpack's default Code Splitting works, but production apps need fine-tuned chunk configuration. Poor chunk config leads to too many tiny files (wasted HTTP overhead), too few large files (poor caching), or duplicated code across chunks.
Real-World Use
A large React SPA with 50+ pages and 200+ npm dependencies uses Webpack splitChunks to create a vendor chunk (React, ReactDOM, React Router), a UI library chunk, a utils chunk, and per-route page chunks. Total chunks: 12. Cache hit rate: 85%.
Webpack Chunk Architecture
graph TD
A[Webpack Bundle] --> B[splitChunks config]
B --> C[Vendor chunk
React, ReactDOM]
B --> D[Common chunk
shared components]
B --> E[Page chunks
per-route code]
B --> F[Async chunks
dynamic imports]
C --> G[Vendors ~250KB
changes rarely]
D --> H[Common ~50KB
changes with code]
E --> I[Pages ~30KB each
changes per feature]
F --> J[Async ~20KB each
loaded on demand]
style C fill:#27ae60,color:#fff
style D fill:#4a90d9,color:#fff
style E fill:#e67e22,color:#fff
style F fill:#f39c12,color:#fff
Basic splitChunks Configuration
// webpack.config.js — Basic splitChunks
module.exports = {
optimization: {
splitChunks: {
// Apply to all chunks (including async)
chunks: 'all',
// Minimum size before splitting (in bytes)
minSize: 20000, // 20KB
maxSize: 244000, // ~240KB (avoid bundles > 250KB)
// Minimum times a module must be shared before splitting
minChunks: 2,
// Maximum parallel requests
maxInitialRequests: 25,
maxAsyncRequests: 10,
// Cache group configuration
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
Advanced Chunk Configuration
// webpack.config.js — Production chunk strategy
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 25,
minSize: 20000,
maxSize: 244000,
// Automatic name generation with content hash
automaticNameDelimiter: '~',
hidePathInfo: true,
cacheGroups: {
// Core React vendor chunk
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'vendor-react',
chunks: 'all',
priority: 30,
enforce: true,
},
// Large utility libraries
utilities: {
test: /[\\/]node_modules[\\/](lodash|moment|date-fns|axios)[\\/]/,
name: 'vendor-utils',
chunks: 'all',
priority: 25,
},
// UI component libraries
ui: {
test: /[\\/]node_modules[\\/](@mui|antd|@chakra-ui|bootstrap)[\\/]/,
name: 'vendor-ui',
chunks: 'all',
priority: 20,
},
// Chart libraries (loaded async)
charts: {
test: /[\\/]node_modules[\\/](chart\.js|d3|echarts|recharts)[\\/]/,
name: 'vendor-charts',
chunks: 'async',
priority: 15,
},
// Shared application code
common: {
name: 'common',
minChunks: 3, // Shared by 3+ modules
priority: 5,
reuseExistingChunk: true,
},
// Styles
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
priority: 10,
},
// Default for everything else
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
// Output configuration for named chunks
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
clean: true,
},
Chunk Naming and Magic Comments
// Magic comments in dynamic imports
const routes = [
{
path: '/dashboard',
// Name the chunk explicitly
component: lazy(() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')),
},
{
path: '/analytics',
// Preload this chunk in priority
component: lazy(() => import(/* webpackChunkName: "analytics" */ /* webpackPreload: true */ './pages/Analytics')),
},
{
path: '/reports',
// Prefetch this chunk (load in idle time)
component: lazy(() => import(/* webpackChunkName: "reports" */ /* webpackPrefetch: true */ './pages/Reports')),
},
{
path: '/admin',
// Group into admin chunk
component: lazy(() => import(/* webpackChunkName: "admin" */ './pages/admin/Dashboard')),
},
{
path: '/admin/users',
// Keep in admin chunk
component: lazy(() => import(/* webpackChunkName: "admin" */ './pages/admin/Users')),
},
{
path: '/heavy-chart',
// Exclude from main chunk
component: lazy(() => import(/* webpackChunkName: "heavy-chart" */ /* webpackMode: "lazy" */ './pages/HeavyChart')),
},
];
// Mode options:
// /* webpackMode: "lazy" */ — Default, load on demand
// /* webpackMode: "lazy-once" */ — Load once, all instances share
// /* webpackMode: "eager" */ — Include in main bundle (no async)
// /* webpackMode: "weak" */ — Only load if already requested
Bundle Analysis
// package.json — Bundle analysis scripts
{
"scripts": {
"build": "webpack --mode production",
"analyze": "ANALYZE=true webpack --mode production",
"analyze:simple": "webpack --mode production --json > stats.json && npx webpack-bundle-analyzer stats.json"
}
}
// webpack.config.js — Bundle analyzer plugin
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
process.env.ANALYZE && new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
generateStatsFile: true,
statsFilename: 'stats.json',
}),
].filter(Boolean),
};
// Custom bundle size checker
// scripts/check-bundle-size.js
const fs = require('fs');
const path = require('path');
const MAX_BUNDLE_SIZE = 300 * 1024; // 300KB for main vendor
const MAX_PAGE_SIZE = 100 * 1024; // 100KB per page chunk
function checkBundleSizes(distDir) {
const files = fs.readdirSync(distDir);
let errors = [];
files.forEach(file => {
if (!file.endsWith('.js')) return;
const size = fs.statSync(path.join(distDir, file)).size;
const sizeKB = (size / 1024).toFixed(1);
if (file.includes('vendor') && size > MAX_BUNDLE_SIZE) {
errors.push(`WARNING: ${file} is ${sizeKB}KB (max ${MAX_BUNDLE_SIZE / 1024}KB)`);
}
if (file.includes('chunk') && size > MAX_PAGE_SIZE) {
errors.push(`WARNING: ${file} is ${sizeKB}KB (max ${MAX_PAGE_SIZE / 1024}KB)`);
}
console.log(`${file}: ${sizeKB}KB`);
});
if (errors.length > 0) {
console.error('\nBundle size warnings:');
errors.forEach(e => console.error(e));
process.exit(1);
}
}
Common Mistakes
- Not configuring splitChunks at all. Webpack's defaults are conservative. Without explicit config, node_modules may end up in every page chunk, causing massive duplication.
- Setting maxInitialRequests too low. A limit of 5 means Webpack may bundle everything into fewer, larger files. Set 20-30 for modern HTTP/2 multiplexing.
- Mixing content hashes with long-term caching. Content hashes invalidate caches on code change. Separate frequently-changing app code from stable vendor code.
- Not analyzing bundles regularly. Bundle bloat creeps in gradually. Run bundle analysis after each major dependency update.
- Creating too many tiny chunks. Each chunk is an HTTP request. With HTTP/1.1, 50+ chunks cause connection bottlenecks. With HTTP/2, 20-30 chunks is acceptable.
Practice Questions
- What does the splitChunks configuration control in Webpack?
- How do you separate vendor code from application code?
- What is the purpose of cacheGroups in splitChunks?
- How do magic comments like webpackChunkName affect chunk naming?
- How do you analyze and visualize Webpack bundle output?
Challenge: Configure Webpack for a production SPA: create separate chunks for React vendor, UI library, common components, and page routes, configure HTTP/2-friendly chunk sizes, add bundle analysis, and keep main vendor under 250KB.
FAQ
Mini Project
Configure Webpack for a multi-page application: set up splitChunks with vendor, common, and page chunk groups, use magic comments for named chunks, add bundle analysis with webpack-bundle-analyzer, implement a bundle size checker in CI, and optimize to keep vendor under 250KB.
What's Next
You've mastered Webpack chunks. Now learn about Vite Dynamic Import for fast Bundling with Vite.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro