Webpack’s content hash filenames are a surprisingly effective way to manage browser caching, but their real power lies in how they decouple asset deployment from cache invalidation.

Let’s see it in action. Imagine we have a simple React app with a single CSS file and a single JS file.

src/index.js:

import './style.css';

console.log('Hello Webpack!');

src/style.css:

body {
  background-color: lightblue;
}

Our webpack.config.js needs to be set up to generate these content hashes. The key is the output.filename and output.chunkFilename properties, using [contenthash].

webpack.config.js:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js', // For main entry points
    chunkFilename: '[name].[contenthash].chunk.js', // For dynamically imported chunks
    path: path.resolve(__dirname, 'dist'),
    clean: true, // Cleans the dist folder before each build
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

When we run npx webpack, the dist folder will contain files like: main.a1b2c3d4e5f6.js main.a1b2c3d4e5f6.css (if using mini-css-extract-plugin)

The [contenthash] is generated based on the content of the file. If the file’s content changes, the hash changes. If it doesn’t change, the hash remains the same. This is the core mechanism for cache busting.

The problem this solves is the "stale content" problem in browsers. Without a mechanism like content hashing, if you update a JavaScript file but keep its filename the same, browsers might continue to serve the old, cached version, leading to unexpected behavior or broken features.

Here’s how it works internally: Webpack calculates a hash for each asset (JS, CSS, etc.) based on its contents. This hash is then embedded into the filename. When you deploy your application, you’re deploying these uniquely hashed files. Your index.html (or whatever entry point you use) will reference these specific filenames.

The critical levers you control are:

  • output.filename and output.chunkFilename: These define the pattern for generated filenames. Using [contenthash] is paramount here.
  • name in entry: This becomes part of the filename before the hash (e.g., main). If you have multiple entry points, they’ll get distinct filenames like bundle1.hash.js and bundle2.hash.js.
  • Plugins: While not strictly part of the output configuration, plugins like mini-css-extract-plugin are essential for extracting CSS into separate files, which also then benefit from content hashing. Without it, CSS might be inlined, and the [contenthash] would apply to the JS bundle containing the styles.

The real magic happens when you need to update your application. If only your index.js file changes, its [contenthash] will change. The generated main.[newhash].js file will be different. However, if your style.css file remains unchanged, its [contenthash] will stay the same, and the main.[samehash].css file will be identical to the previous deployment. This means browsers can continue to use their cached version of the CSS file, only downloading the new JavaScript. This significantly reduces download sizes and speeds up deployments for users when only a small part of the application has changed.

The most common way to get these filenames into your HTML is by using an HTML plugin that automatically injects script and link tags based on the generated Webpack output. For instance, html-webpack-plugin can read the generated [contenthash] filenames and correctly reference them in your index.html.

The next step is understanding how to manage the index.html file itself, ensuring it’s correctly updated to point to the new hashed assets with each build.

Want structured learning?

Take the full Webpack course →