Webpack’s content hashing for long-term caching is less about "content" and more about "change detection."

Let’s see it in action. Imagine we have a simple webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
};

And our src/index.js:

console.log('Hello, Webpack Caching!');

And src/index.html:

<!DOCTYPE html>
<html>
<head>
  <title>Webpack Cache Test</title>
</head>
<body>
  <h1>Webpack Caching Example</h1>
</body>
</html>

Running npx webpack will produce output like this:

asset main.a1b2c3d4e5f678901234.js 1.23 KiB [emitted] [immutable] [contenthash]
asset index.a1b2c3d4e5f678901234.js 1.23 KiB [emitted] [immutable] [contenthash]
asset index.html 678 bytes [emitted]

Notice the [contenthash] in the output filename. If we change src/index.js to:

console.log('Hello, Webpack Caching Again!');

And run npx webpack again:

asset main.f0e9d8c7b6a543210987.js 1.23 KiB [emitted] [immutable] [contenthash]
asset index.f0e9d8c7b6a543210987.js 1.23 KiB [emitted] [immutable] [contenthash]
asset index.html 678 bytes [emitted]

The hashes in the .js files have changed, but index.html remains the same. This is the core of content hashing: the hash is generated based on the content of the asset. When the content changes, the hash changes, invalidating the cache for that specific file.

The problem Webpack’s content hashing solves is cache busting. When you deploy new versions of your application, you want users to download the latest assets. If assets have static names (e.g., bundle.js), browsers will serve the cached version indefinitely, leading to users running outdated code. By including a unique hash in the filename, any change to the asset’s content results in a new filename. Browsers, seeing a new filename, will fetch the new version.

Internally, Webpack calculates a hash for each asset based on its content, including its dependencies. This hash is then appended to the filename. When HtmlWebpackPlugin processes your index.html, it injects <script> tags that reference these hashed filenames.

The key levers you control are:

  • output.filename: This is where you specify the pattern for your output filenames, using placeholders like [name] for the chunk name and [contenthash] for the content hash. For production, [name].[contenthash].js is a common pattern.
  • output.chunkFilename: This dictates the naming convention for emitted chunks that are not the main entry point (e.g., dynamically imported modules). It also uses [contenthash].
  • output.path: The directory where the output files are placed.
  • plugins: Especially HtmlWebpackPlugin, which is responsible for injecting the hashed asset URLs into your HTML.

The most surprising true thing about [contenthash] is that it’s not just the content of that single file. Webpack’s hashing mechanism for [contenthash] takes into account the content of the entire module graph that contributes to that chunk. If a dependency of a module changes, and that change propagates to a chunk, the [contenthash] of that chunk will change, even if the chunk’s "own" code hasn’t been modified. This ensures that any change upstream in your module tree correctly invalidates the cache for dependent bundles.

The next logical step is understanding how to manage different hashing strategies, like [chunkhash] and [fullhash], and when to use them for optimal caching and deployment.

Want structured learning?

Take the full Webpack course →