Cut Webpack 5 Rebuild Times with Persistent Filesystem Caching
Enable webpack 5's persistent filesystem cache to make repeat builds and CI runs dramatically faster — with the configuration, verification steps, and stale-cache recovery you need.
13 Nov 2025, 15:04 UTC

If your webpack builds take minutes on a cold start and barely less on repeat runs, you are recompiling work that has not changed. Webpack 5's persistent filesystem cache serializes the compiler's module graph, resolutions, and built modules to disk, so the second build — even in a fresh process or on a CI agent — reuses it. On large projects this commonly turns a multi-minute cold build into a small fraction of that time. This guide covers enabling it correctly, verifying it actually helps, and recovering when the cache serves stale output.
Prerequisites
- webpack 5.x. This feature does not exist in webpack 4, where
cache: truewas a boolean with different semantics. Check withnpx webpack --version. - A Node.js version supported by your webpack release.
- A build slow enough to matter. Cache serialization has overhead; on tiny projects the cache can be neutral or slightly negative. Measure before and after.
Enable the filesystem cache
In webpack.config.js, set cache.type to 'filesystem':
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
cache: {
type: 'filesystem',
// Invalidate the cache automatically when these files change.
buildDependencies: {
config: [__filename],
},
// Separate caches per build variant so dev and prod never share state.
name: 'prod',
cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
},
};Three decisions matter here:
buildDependencies.configtells webpack which files the cache depends on. When your config changes, the cache invalidates instead of silently reusing output built with old settings. Include any file your config reads (e.g. a shared babel config).cache.nameisolates build variants. If you run dev and prod builds from the same config file, give each a distinct name ('dev'/'prod') or they will contaminate each other's cache.cacheDirectorydefaults tonode_modules/.cache/webpack. Setting it explicitly makes CI caching configuration easier.
Verify the cache is working
- Run your build twice in a row:
npx webpack --config webpack.config.js(run in your project root; no special permissions needed). The second run should be substantially faster. If times are identical, the cache is not being hit. - Confirm the cache directory exists and grows:
ls node_modules/.cache/webpack. You should see pack files created and updated between runs. - For deeper confirmation, enable infrastructure logging to see cache activity:
npx webpack --config webpack.config.js --infrastructure-log-level log(or setinfrastructureLogging: { level: 'log' }in config). Look for cache restore/store messages rather than full recompilation. - Correctness check: on a clean checkout, diff the emitted output (ignoring content hashes in filenames) with cache enabled versus
cache: false. The assets should be equivalent.
CI: persist the cache between runs
The big win in CI is restoring node_modules/.cache/webpack on cold agents. Cache that directory keyed by lockfile hash plus a webpack version string, for example:
# Generic CI cache step (adapt to your CI system)
key: webpack-${{ hashFiles('package-lock.json') }}-v1
paths:
- node_modules/.cache/webpackBump the v1 suffix whenever you upgrade webpack, loaders, or plugins — that is your manual cache-bust lever. Two cautions for CI:
- Serialized cache data can be sensitive to absolute paths. If your CI restores the cache into a different checkout directory than the one that produced it, validate that hits still occur; otherwise set
cacheDirectoryto a stable absolute path or accept a cold first build. - Do not cache
node_modules/.cacheblindly across major toolchain changes; a stale cache that survives a loader upgrade is exactly how confusing stale-output bugs start.
When the cache misbehaves: stale output and recovery
Webpack automatically tracks source files and declared buildDependencies. The failure mode is loaders or plugins that read undeclared external state — environment variables, generated files, network-fetched config. Their inputs are invisible to webpack's dependency tracking, so the cache can serve stale output.
Diagnose it deliberately: change a value your custom loader reads from an external file (one not declared as a dependency), rebuild, and check whether the output reflects the change. If it does not, you have an invalidation gap. Fixes, in order of preference:
- Declare the external input in
cache.buildDependenciesso changes invalidate the cache. - Set
cache.versionto a string derived from the external state (e.g. an env var value), forcing a fresh cache when it changes. - As a blunt recovery, delete the cache directory:
rm -rf node_modules/.cache/webpack(run in the project root; safe — webpack rebuilds it on the next run). This is the standard recovery whenever builds behave inconsistently after toolchain changes.
Limitations and housekeeping
- Disk growth: on large monorepos the cache can grow to gigabytes. Use
cache.maxAge(in milliseconds) to expire stale entries, or schedule periodic cleanup of the cache directory. - Version drift: defaults and invalidation edge cases vary across webpack 5 minor releases. Confirm behavior against the version you actually have installed, and re-verify after upgrades.
- Audit custom loaders before trusting the cache in production builds — any loader reading undeclared state is a stale-output risk.
The payoff for a few lines of configuration is large: faster local rebuilds, faster CI, and a well-defined recovery path (delete the directory or bump cache.version) when something looks wrong.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.