Serving unminified, unbundled JavaScript and CSS wastes bandwidth and slows page loads. This guide covers the build-time optimization every production frontend deployment should include.
Why Minification and Bundling Matter
- Smaller file sizes mean faster downloads, especially on slower connections
- Bundling reduces the number of separate HTTP requests needed to load a page
- Removes comments, whitespace, and dead code not needed in production
Using Vite (Modern, Fast Default Choice)
npm create vite@latest myapp
cd myapp
npm install
npm run build
Vite automatically minifies, bundles, and content-hashes filenames in its production build with zero additional configuration for most projects.
Understanding the Build Output
dist/
├── index.html
├── assets/
│ ├── index-a1b2c3d4.js
│ ├── index-e5f6g7h8.css
└── ...
The hash in each filename changes whenever the file's content changes — enabling safe, aggressive long-term caching (see How to Configure Browser Caching and Cache-Busting for Static Assets).
Configuring Additional Optimizations in Vite
// vite.config.js
export default {
build: {
minify: 'esbuild',
sourcemap: false, // disable in production for smaller output, unless needed for error tracking
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
};
manualChunks separates third-party library code from your application code, letting browsers cache the (rarely-changing) vendor bundle separately from your (frequently-changing) application code.
Using Webpack (Alternative Bundler)
npm install --save-dev webpack webpack-cli terser-webpack-plugin
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
minimize: true,
splitChunks: { chunks: 'all' },
},
};
Minifying CSS
Most modern bundlers (Vite, Webpack with appropriate loaders) handle CSS minification automatically as part of the build — verify your specific setup includes this rather than assuming.
Tree Shaking (Removing Unused Code)
Modern bundlers automatically eliminate code that's imported but never actually used, provided your code uses ES modules (import/export) rather than CommonJS (require), which doesn't support tree shaking as effectively.
Analyzing Bundle Size
npm install --save-dev rollup-plugin-visualizer
Generates a visual breakdown of what's contributing to your bundle size — useful for identifying unexpectedly large dependencies worth reconsidering or lazy-loading.
Code Splitting for Large Applications
const Dashboard = React.lazy(() => import('./Dashboard'));
Loads code only when actually needed (e.g. when navigating to a specific route), rather than including everything in the initial page load — significantly improves initial load time for larger applications.
Verifying the Optimized Build
ls -lh dist/assets/
Compare against an unminified/development build to confirm the size reduction, and test the production build locally before deploying:
npm run preview
Common Errors
Production build works differently than development — usually caused by environment-specific code paths or missing production environment variables; verify configuration matches expectations for the build mode.
Bundle size unexpectedly large — use a bundle analyzer to identify the actual cause rather than guessing; often a single large, unnecessarily-imported dependency.
Best Practices
- Always deploy the production build, never the unminified development build
- Use code splitting for larger applications to improve initial load time
- Periodically analyze bundle size to catch unexpected bloat
Continue Reading
- How to Configure Browser Caching and Cache-Busting for Static Assets
- How to Perform a Complete Website Speed Audit
- How to Deploy a Vue.js/React Single Page Application
Browse more articles in Static Site Hosting & Frontend Deployment.
