Vite · intermediate
Vite optimized dependency mismatch: explain a stale prebundle
When Vite surfaces a "Pre-transform error" or "Outdated optimize dep" notification, the dev server is serving a prebundled dependency cache whose hash no longer matches the resolved module graph on disk. This guide explains how to recognize that exact mismatch, distinguish it from a real source error, and rebuild the cache with verifiable proof.
The symptoms
- •Dev server console prints "Pre-transform error: ... out of date: ... old hash ... new hash" or "Outdated optimize dep" banner.
- •Browser shows a stale module: HMR updates arrive but the runtime evaluates against a cached prebundle, so edits to a dependency are not reflected.
- •Production build succeeds, but `vite dev` still complains, indicating the failure is in the in-memory optimize graph rather than source code.
- •Removing and reinstalling node_modules temporarily resolves the issue, but it returns after the next dependency change.
- •Stack traces reference `.vite/deps/` paths or `vite:dep-bundle` even though the originating source lives in `node_modules/`.
Likely causes
- •A `package.json` upgrade, lockfile re-resolution, or `npm install` / `pnpm install` / `yarn install` rotated versions while `node_modules/.vite/deps` was left untouched.
- •`optimizeDeps.entries` or `optimizeDeps.include` was edited without deleting the metadata cache, so Vite cannot reconcile the new graph with the stored hashes.
- •Monorepo hoisting or workspace symlinks changed the resolved path of a dependency, invalidating the on-disk prebundle but not the cache index.
- •A platform-level rebuild (e.g., native dependency swap, Node version change) altered package contents while preserving file timestamps that Vite used for change detection.
- •Browser-served HTML or worker imports reference a module URL that no longer exists in the freshly resolved graph.
First ten minutes
- 01Capture the exact console output: the "old hash" vs "new hash" strings, the offending dependency name, and the file path reported by Vite's optimizer banner.
- 02Compare `package.json` and the lockfile against the last known-good commit; note any dependency version drift in the prebundled package.
- 03Stop the running dev server so no process holds files inside `node_modules/.vite/`. Stop only after recording its <pid> if you need to inspect open handles later.
- 04List `node_modules/.vite/deps/` and `node_modules/.vite/deps_temp_*` to confirm whether a partial rewrite was left behind alongside the stale cache.
- 05Cross-check `vite.config.*` for `optimizeDeps.entries`, `optimizeDeps.include`, and `optimizeDeps.exclude`; note any field that changed since the last successful boot.
Evidence to collect
- •Full Vite dev server log including the "outdated optimize dep" banner and the conflicting hash pair.
- •Output of `ls -la node_modules/.vite/deps/ node_modules/.vite/deps_temp_*/` showing cache directory state and timestamps.
- •Diff of `package.json` and lockfile against the last commit that booted cleanly.
- •Current values of `optimizeDeps` configuration and any environment variables that override include/exclude lists.
- •Browser-side Module Map: the resolved URL of the failing import as DevTools records it, versus the file Vite claims should serve.
Where to look
- •Vite dev server stdout and stderr; the optimizer banner is emitted before the server reports "ready".
- •The boundary between `node_modules/.vite/deps/` (cached prebundles) and `node_modules/<pkg>/` (freshly resolved source).
- •`vite.config.*` at project root and any environment-specific config overlay that sets `optimizeDeps`.
- •Lockfile resolution metadata for the affected dependency: `node_modules/.pnpm/`, `node_modules/.yarn/`, or the corresponding workspace store path.
- •Browser DevTools Network tab: the failing module URL should map back to a `?v=` query string whose hash prefix matches the "new hash" reported by Vite.
Diagnostic steps
- 01If the banner shows a hash pair, diff the "old" hash against the cache index and the "new" hash against a freshly hashed copy of the resolved module; identical hashes mean the cache is simply stale, divergent hashes mean source drift.
- 02Compute the dependency's resolved path from the lockfile and confirm it equals the path Vite reports; mismatches indicate hoisting or workspace drift rather than a real prebundle fault.
- 03Re-read `optimizeDeps.entries` and `optimizeDeps.include`; if the suspect package appears in either list but not in `exclude`, the prebundle is intentional and a rebuild is the correct response.
- 04Confirm no second Vite process is holding `node_modules/.vite/deps/`: duplicate processes can prevent rewrites and emit repeated mismatch banners after each restart.
- 05If the message names a transitive dependency, trace it through the import graph to the root package that imports it; the root package is the entry the optimizer actually scanned.
- 06Compare a single failing import against the same import in `vite build`; a build-only failure points to a production optimizer configuration, a dev-only failure confirms a prebundle mismatch.
Common mistakes
- •Editing dependency source code inside `node_modules/` and expecting HMR to pick it up; Vite serves the prebundle, not the patched file, so edits appear to do nothing.
- •Running `vite dev` again immediately after changing `package.json`; the cache invalidation logic only re-scans after the process restarts, and partial temp directories can confuse the next boot.
- •Deleting `node_modules/.vite/deps/` while another `vite` process is still running; this can leave a half-rewritten cache and trigger the same banner on the next start.
- •Adding the failing package to `optimizeDeps.exclude` to silence the banner; this only hides the symptom and does not align the served graph with the source graph.
- •Trusting the browser console stack trace as evidence of a source bug; the trace originates from the prebundle and reflects the cached transform, not the original module.
Safe fixes
- •Stop every Vite dev process for the project, then delete `node_modules/.vite/deps/` and any `node_modules/.vite/deps_temp_*/` directory before restarting the dev server; this forces a full rescan from the current resolved graph.
- •If the mismatch recurs after a lockfile change, re-run the package manager's install command so `node_modules/` matches the lockfile before the next `vite dev` start.
- •For monorepos, run the install at the workspace root and confirm each package manager field (`hoist`, `public-hoist-pattern`) has not been altered since the last clean boot.
- •Pin `optimizeDeps.entries` to the actual HTML entry files; an entry that no longer exists produces a phantom mismatch that survives cache rebuilds.
- •Document the "old hash" and "new hash" values from the banner before clearing them, so the next regression can be diffed against this incident.
Prove the fix
- 01Restart `vite dev` and confirm the banner no longer appears and the server reaches the "ready" state without an "outdated optimize dep" message.
- 02In DevTools, fetch the previously failing import URL and verify the served file path lies inside `node_modules/.vite/deps/<pkg>.js` with the new hash prefix, not inside `node_modules/<pkg>/`.
- 03Edit a non-functional comment inside the dependency's source, repeat `vite dev`; an immediate HMR update on the consumer module confirms the prebundle now reflects the resolved graph.
- 04Run `vite build` for the same project; it must complete without invoking the optimizer's "force re-bundle" path, which indicates the dev and production graphs agree.
- 05Save the post-fix `node_modules/.vite/deps/_metadata.json` hash alongside the banner's "new hash" so future incidents can be checked against the same baseline.
Prevention and next steps
- •After every dependency change, run the package manager's install command and then restart the dev server; never restart Vite while `node_modules/` differs from the lockfile.
- •Keep `optimizeDeps.include` and `optimizeDeps.exclude` lists reviewed in code review so silent configuration drift cannot desynchronize the prebundle.
- •Add a CI step that performs a clean `node_modules/` install and a `vite build` to detect cache-only failures that would otherwise hide until a developer restarts their machine.
- •Treat `node_modules/.vite/` as a build artifact: do not commit it, do not symlink it across worktrees, and exclude it from filesystem watchers that could trigger premature invalidation.
Safe commands and checks
ls -la node_modules/.vite/deps/ node_modules/.vite/deps_temp_*/ 2>/dev/null || true cat node_modules/.vite/deps/_metadata.json | head -n 50 grep -nE "optimizeDeps" vite.config.* vite.config.*.ts vite.config.*.js 2>/dev/null || true diff <(git show HEAD:package.json) package.json diff <(git show HEAD:<lockfile>) <lockfile> ps -eo pid,comm | grep -E "vite|node" | grep -v grep || true Stop the dev server and rename the project-local Vite dependency-cache directory to <cache-backup> before restarting; record the old and new metadata hashes.