Webpack 5 remains the dominant bundler for large-scale production applications, particularly those requiring Module Federation, complex code splitting, or migration from legacy setups. This guide covers everything from basic loaders to advanced optimization techniques that reduce bundle sizes by 40-60%.
Webpack 5 Basics and Entry/Output
The webpack.config.js file controls all bundling behavior. Understanding the core concepts — entry, output, mode, and resolve — is essential before diving into loaders and plugins.
// webpack.config.js — Complete Production Config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDev ? 'development' : 'production',
entry: {
main: './src/index.tsx',
// Multiple entry points for different pages
// admin: './src/admin/index.tsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isDev ? '[name].js' : '[name].[contenthash:8].js',
chunkFilename: isDev ? '[name].chunk.js' : '[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash:8][ext][query]',
clean: true, // Remove old files on each build
publicPath: '/',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
},
},
// Persistent filesystem cache — huge speed improvement for incremental builds
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename], // Invalidate cache when config changes
},
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
compress: true,
},
};Essential Loaders
Loaders transform files before they are added to the dependency graph. Each loader handles a specific file type or transformation.
// Essential Webpack Loaders Configuration
module.exports = {
module: {
rules: [
// 1. TypeScript/JavaScript with Babel
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: '>0.25%, not dead' }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
// Cache Babel results for faster rebuilds
cacheDirectory: true,
cacheCompression: false,
},
},
},
// 2. CSS / SCSS / PostCSS
{
test: /\.module\.(css|scss)$/, // CSS Modules
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { modules: true, importLoaders: 2 },
},
'postcss-loader',
'sass-loader',
],
},
{
test: /(?!\.module)\.(css|scss)$/, // Global CSS
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
// 3. Images and fonts (Webpack 5 Asset Modules — no loader needed)
{
test: /\.(png|jpg|jpeg|gif|webp)$/i,
type: 'asset', // Auto-chooses between inline and resource
parser: {
dataUrlCondition: { maxSize: 10 * 1024 }, // Inline if < 10KB
},
},
{
test: /\.svg$/,
use: ['@svgr/webpack'], // Import SVGs as React components
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
};Code Splitting and Lazy Loading
Code splitting is Webpack's most important optimization feature. It splits your code into multiple bundles that can be loaded on demand, reducing initial load time.
// Code Splitting and Lazy Loading
// 1. Dynamic imports (automatic code splitting)
// React.lazy creates a separate chunk for each lazy component
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// 2. Manual chunk naming with magic comments
const HeavyChart = lazy(
() => import(/* webpackChunkName: "charts" */ './components/HeavyChart')
);
// 3. Webpack SplitChunksPlugin configuration
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vendor chunk: node_modules
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 20,
},
// Separate React from other vendors
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 30,
},
// Shared chunks used by 2+ modules
common: {
name: 'common',
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
runtimeChunk: 'single', // Separate runtime chunk
},
};Production Optimization
Production builds require aggressive optimization to minimize bundle sizes. Webpack 5's built-in optimizations plus careful configuration can achieve significant size reductions.
// Production Optimization Configuration
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // Use all CPU cores
terserOptions: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
pure_funcs: ['console.log'],
},
format: { comments: false },
},
}),
new CssMinimizerPlugin(),
],
// Tree shaking — only include used exports
usedExports: true,
sideEffects: true, // Respect package.json sideEffects field
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
}),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
},
}),
// Only add analyzer when needed: ANALYZE=true npm run build
process.env.ANALYZE && new BundleAnalyzerPlugin(),
].filter(Boolean),
};Frequently Asked Questions
When should I use Webpack instead of Vite?
Choose Webpack for: Module Federation (micro-frontends), very large existing codebases with complex Webpack configs, specific loaders with no Vite equivalent, or when you need IE11 support. For new projects in 2026, Vite is usually the better choice.
How do I analyze my Webpack bundle size?
Use webpack-bundle-analyzer: npm install --save-dev webpack-bundle-analyzer, then add it as a plugin with BundleAnalyzerPlugin. Run your build and it opens an interactive treemap showing what's consuming your bundle size. Focus on the largest rectangles first.
What is Module Federation in Webpack 5?
Module Federation allows multiple separate Webpack builds to share code at runtime without a shared build process. Each application can expose components/modules and consume from others. It's the primary architecture for micro-frontends at scale.
Why is my Webpack build so slow?
Common causes: (1) No persistent caching (add cache: { type: "filesystem" }), (2) Too many loaders in chain, (3) babel-loader without exclude: /node_modules/, (4) large devtool source maps in development, (5) not using thread-loader for parallel builds. Enable cache first as it provides the biggest speedup.