Skip to content

How to Minify CSS and JavaScript for Faster Loading

Last Updated: September 3, 2026

What Minification Actually Removes

Minification strips every byte from your CSS and JavaScript files that is not required for the code to function correctly. This includes whitespace (spaces, tabs, line breaks), comments (both single-line and block), shortening of variable names, removal of unused code paths, and consolidation of duplicate declarations. The resulting file is functionally identical to the original but significantly smaller in byte size.

Minification does not change how your code behaves. A minified JavaScript file executes exactly the same logic as the unminified version. The minifier only removes human-readable formatting that the browser does not need to parse and execute. This is fundamentally different from obfuscation, which intentionally makes code hard to read for security purposes.

Typical minification savings vary by code style. A well-formatted CSS file with generous comments, consistent indentation, and descriptive variable names reduces by 40-60% when minified. A JavaScript file with JSDoc comments, console.log statements, and verbose formatting reduces by 30-50%. These savings directly reduce the bytes your browser needs to download, parse, and execute.

Minifying CSS with CSSNano

CSSNano is the most widely used CSS minifier. It operates as a PostCSS plugin, which means it integrates into PostCSS-based build pipelines alongside autoprefixer, postcss-import, and other transforms. CSSNano performs multiple optimization passes, collapsing declarations, merging rules, shortening color values, and removing redundant code:

// postcss.config.js
module.exports = {
  plugins: [
    require('postcss-import'),
    require('autoprefixer'),
    require('cssnano')({
      preset: ['default', {
        discardComments: { removeAll: true },
        reduceIdents: true,
        mergeRules: true,
      }]
    })
  ]
}

CSSNano's default preset handles most optimization needs. For additional savings, enable the advanced preset which performs more aggressive transformations like merging adjacent rules with similar selectors, recalculating color values to shorter representations, and restructuring shorthand properties:

require('cssnano')({
  preset: 'advanced'
})

Run CSSNano on your compiled CSS file as the final step in your build process. The input is your concatenated, prefixed CSS. The output is a single minified file ready for production deployment.

Minifying JavaScript with Terser

Terser is the standard JavaScript minifier used by Webpack, Vite, and most modern build tools. It performs dead code elimination, variable name mangling, syntax simplification, and whitespace removal. Terser replaced UglifyJS as the community standard because it supports ES6+ syntax while maintaining fast execution speeds.

// terser.config.js
module.exports = {
  compress: {
    drop_console: true,       // Remove console.log statements
    drop_debugger: true,      // Remove debugger statements
    passes: 2,                // Multiple optimization passes
    pure_funcs: ['console.log'], // Treat these as side-effect-free
  },
  mangle: {
    toplevel: true,           // Mangle top-level variable names
    properties: false,        // Do not mangle property names
  },
  output: {
    comments: false,          // Remove all comments
    ecma: 2020,               // Output modern syntax
  }
}

The drop_console option is particularly important for production. Console statements left in production code expose internal state to users and add unnecessary bytes. Terser removes them automatically during minification. The passes: 2 option runs the optimizer twice, catching optimization opportunities that a single pass misses.

Webpack Configuration for Minification

Webpack 5 uses TerserPlugin by default in production mode, but you can customize the configuration for better results:

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: { passes: 2, drop_console: true },
          output: { comments: false },
        },
      }),
      new CssMinimizerPlugin(),
    ],
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 10,
      maxSize: 244000, // 244KB before splitting
    },
  },
};

The splitChunks configuration works alongside minification to reduce initial load time. Instead of serving a single large minified bundle, Webpack splits code into smaller chunks loaded on demand. Combined with minification, this approach reduces both the size of individual requests and the number of bytes loaded upfront.

Vite Minification Setup

Vite uses esbuild for JavaScript minification (10-100x faster than Terser) and CSSNano for CSS. The default configuration handles minification automatically in production builds, but you can customize the behavior:

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    minify: 'esbuild', // Default: use esbuild for JS
    // Alternative: 'terser' for more control
    cssMinify: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash-es', 'date-fns'],
        },
      },
    },
  },
});

Vite's esbuild minifier supports tree shaking out of the box, removing unused exports from your JavaScript bundles. This combined with minification produces significantly smaller output than minification alone. The build command generates both minified and unminified versions, with sourcemaps for debugging production issues.

Before and After: Measuring Results

Measure the impact of minification using your browser's Network tab. Disable cache, reload the page, and compare the transfer sizes. A typical measurement reveals significant differences:

# Without minification:
style.css: 85KB (transferred: 23KB with gzip)
app.js: 420KB (transferred: 112KB with gzip)

# After minification + gzip:
style.css: 48KB (transferred: 11KB with gzip)
app.js: 265KB (transferred: 68KB with gzip)

# After minification + Brotli:
style.css: 48KB (transferred: 8KB with Brotli)
app.js: 265KB (transferred: 52KB with Brotli)

The combined effect of minification plus compression produces dramatically smaller transfers than either optimization alone. A 420KB JavaScript file minifies to 265KB, then compresses to 52KB with Brotli. The user downloads 52KB instead of 420KB, reducing both download time and parse time.

Use our page speed checker to measure the before-and-after impact of minification on your specific pages. The tool reports transfer sizes, compression ratios, and identifies which files benefit most from minification.

Testing Minified Output

Minification can occasionally break code that relies on specific formatting, like multi-line template literals with intentional indentation, eval() statements, or code that references Function.prototype.toString(). Test your minified output by running your full test suite against the production build. If tests pass against the minified version, the minification is safe.

Common minification issues include: renamed variables breaking dynamic property access (obj[variable] where the variable name was mangled), preserved comments that contain code-like syntax confusing the minifier, and incorrect sourcemap offsets making debugging difficult. Check your browser's console for JavaScript errors after deploying minified code, and verify that all interactive features function correctly.

Sourcemaps for Debugging

Sourcemaps create a mapping between minified code and your original source files, enabling you to debug production issues using readable variable names and line numbers. Generate sourcemaps by enabling the devtool option in your build configuration. Webpack supports multiple sourcemap quality levels, from cheap-module-source-map for fast builds to hidden-source-map for production debugging without exposing sources:

// webpack.config.js
module.exports = {
  devtool: 'hidden-source-map', // Generates map without reference comment
};

Host sourcemaps on your server or upload them to an error monitoring service like Sentry or Datadog. Never serve sourcemaps publicly in production because they reveal your source code structure. Configure your server to restrict sourcemap access to authenticated developers or internal IP ranges.