Vite · beginner
How to verify Vite dependency invalidation after an upgrade
How to verify that Vite's dependency pre-bundle graph stops serving transformed output from the previous version after an upgrade, including cold-cache checks, metadata comparison, and an HMR-based regression test.
The symptoms
- •Stale transforms served after upgrade: the dev server returns code referencing symbols, exports, or option names that exist only in the previous dependency version while source modules were updated to the new version.
- •Runtime errors such as `X is not a function`, `Cannot read properties of undefined`, or `Unexpected token` that disappear when `node_modules/.vite` is removed and the server is restarted with a cold cache.
- •Browser DevTools Network panel shows requests for pre-bundled chunks returning 200 with content matching the pre-upgrade version, even after a full page reload that bypasses HMR.
- •Vite logs report no fresh `optimizeDeps` run after restart despite `package.json` listing a newer dependency version, indicating the cache key did not change.
- •Source maps for transformed dependencies still reference paths inside the old dependency directory tree after upgrade, while the installed version on disk has moved or changed contents.
Likely causes
- •Vite's dependency cache key derived from `package.json` lockfile entries did not change in a way Vite recognizes, so `optimizeDeps` was skipped and `node_modules/.vite/deps/_metadata.json` still references the old pre-bundle hash.
- •The `optimizeDeps.include` or `optimizeDeps.exclude` lists still match the previous package name or entry after a rename, leaving stale pre-bundled entries that conflict with the new package.
- •A dependency was upgraded in `package.json` but the lockfile (`package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`) was not refreshed in a way Vite reads, so Vite's discovery step retains the prior dependency metadata snapshot.
- •Globals exposed via `optimizeDeps.extensions` or `define` still reference symbols from the previous version, and the cached transformed module exposes those globals while the new package no longer does.
- •A monorepo tool such as pnpm or Yarn PnP changes the on-disk path of a dependency without updating the `hash` field inside `node_modules/.vite/deps/_metadata.json`, leaving Vite's cache pointing at a now-stale resolved file.
First ten minutes
- 01Record the current state: capture the Vite version, the upgraded dependency's `name` and version from `package.json`, and the timestamp of `node_modules/.vite/deps/_metadata.json` if it exists.
- 02Reproduce the symptom on demand: trigger a full page reload (not HMR) and inspect the Network panel for a pre-bundled chunk; record whether its body content matches the new or old dependency version.
- 03Compare on-disk evidence: diff the dependency's installed `package.json` against the version pinned in the lockfile to confirm the upgrade took effect at the filesystem level.
- 04Force a cold-cache verification pass: stop the dev server, delete only `node_modules/.vite`, restart Vite, and observe whether `optimizeDeps` logs a fresh run and whether the symptom disappears.
- 05If the symptom disappears on cold cache, do not declare victory — re-warm the cache by performing at least one full dependency import through the running dev server before continuing the verification.
Evidence to collect
- •Vite version string from `vite --version` (or from `node_modules/vite/package.json` `"version"`) recorded alongside the dependency's pre-upgrade and post-upgrade versions.
- •Contents of `node_modules/.vite/deps/_metadata.json` (specifically the top-level `hash` field and the entries under `browserHash` / `hash`) before and after the dev server start, used to confirm whether Vite recomputed the cache key.
- •A copy of the pre-bundled dependency chunk served by the dev server (retrieved via the same-origin path that the browser used) compared character-for-character against the transform output produced after a cold cache.
- •The `optimizeDeps` section of the dev server log, including the dependency list logged at startup and any line indicating the cache was reused versus re-optimized.
- •Lockfile entry for the upgraded dependency (lockfile version, resolved URL/registry key, and integrity hash) to confirm that the version Vite saw during discovery matches what is actually installed.
Where to look
- •The boundary between Vite's dependency optimizer (which writes `node_modules/.vite/deps`) and the on-disk dependency tree inside `node_modules/<dep>/`; mismatches here are the dominant failure surface after upgrades.
- •The `optimizeDeps` section of `vite.config.*` plus its resolved form, since `include`, `exclude`, and `extensions` directly determine which packages are pre-bundled and which entry point is used.
- •The `vite` key in `package.json` of the project (for version) and the corresponding entry in the lockfile, since Vite uses its own version as part of the pre-bundle cache key computation.
- •The `node_modules/.vite/deps/_metadata.json` boundary, whose `hash` field is what Vite compares against new discovery output to decide between cache reuse and re-optimization.
- •The DevTools Network panel boundary for `/node_modules/.vite/deps/<dep>.js?v=...` requests, which is the only place where the browser-visible transformed output of a pre-bundled dependency is observable.
Diagnostic steps
- 01Step 1 — Confirm the dependency version on disk: read `node_modules/<dep>/package.json` `"version"` and compare it to the version in `package.json` and the lockfile entry to prove the upgrade is actually in place; a mismatch means the upgrade was not real and is outside Vite's responsibility.
- 02Step 2 — Observe the dev server's pre-bundle decision: start the dev server and capture the startup log line that lists the optimized dependencies; if the upgraded dependency is absent, `optimizeDeps.exclude` or a manual chunking config is bypassing pre-bundling and the verification scope changes.
- 03Step 3 — Read `_metadata.json` before any restart: record the `hash`, the per-dependency `file` paths, and the `browserHash`; these are the values that will only change if Vite decides the cache is stale.
- 04Step 4 — Retrieve a cold-cache transformed output: stop the dev server, delete only `node_modules/.vite`, restart, trigger a full reload that imports the upgraded dependency, and save the served pre-bundled chunk body as the post-upgrade baseline.
- 05Step 5 — Retrieve a warm-cache transformed output: with the dev server still running, perform a full reload that imports the upgraded dependency again and save the new served chunk body; if it differs from step 4, the warm cache is not being reused and the invalidation target is already working.
- 06Step 6 — Compare `_metadata.json` from step 3 against the post-restart file; if the top-level `hash` is unchanged despite a dependency upgrade, Vite failed to detect the upgrade and the verification reports a non-regression only with caveats.
- 07Step 7 — If steps 1–6 show cache reuse is the issue, evaluate whether the upgrade changed the dependency's published `exports` map or `main` / `module` fields; a mismatch between what `package.json` advertises and what Vite discovered is the most common reason Vite keeps the old transform.
Common mistakes
- •Trusting an HMR reload as evidence: HMR delivers updated modules without invalidating pre-bundled dependencies, so a green HMR session proves nothing about whether `node_modules/.vite` was rebuilt.
- •Stopping only the browser tab instead of the dev server: the cache is held by the Vite process, not the browser, so the tab refresh will reuse the old pre-bundled chunk even when the file on disk is new.
- •Deleting `node_modules` instead of `node_modules/.vite`: while deleting the cache directory is correct, blowing away `node_modules` removes the on-disk evidence that is needed for step 1 of the diagnostic sequence.
- •Skipping the lockfile check: Vite's cache key derivation relies on resolved metadata from package managers, and a `package.json` bump without a lockfile refresh is the most common reason the cache key does not change.
- •Concluding that a missing `optimizeDeps` log line proves reuse: Vite only logs re-optimization explicitly when verbose logging is enabled, so silence is not a signal that the pre-bundle was actually re-run.
Safe fixes
- •Conditional fix A — Force one cold pre-bundle pass: stop the dev server, delete only the `node_modules/.vite` directory, restart Vite, and confirm `_metadata.json` is rewritten and the served chunk content for the upgraded dependency matches the post-upgrade source. Apply only when `_metadata.json` `hash` is unchanged across an upgrade.
- •Conditional fix B — Correct the lockfile input: if the lockfile still pins the pre-upgrade version while `package.json` lists the new version, run the package manager's install command to regenerate the lockfile entry, then re-apply conditional fix A. Apply only when the lockfile and `package.json` disagree.
- •Conditional fix C — Pin the discovery entry point: when an upgraded dependency reorganizes its `exports` map, set `optimizeDeps.include` to the explicit subpath that the application actually imports, so Vite's discovery aligns with the new entry points. Apply only when step 7 reveals a changed `exports` or `main` field on the upgraded package.
- •Conditional fix D — Bump Vite's own version in the cache key scope: when upgrading Vite itself, treat the cache as untrusted by default and apply conditional fix A, because Vite uses its own version string in the cache key and a same-day Vite bump can keep stale pre-bundle binaries valid in surprising ways.
- •Do not edit `_metadata.json` directly: the file format is internal to Vite and manipulating it removes the empirical evidence that subsequent verification steps depend on.
Prove the fix
- 01Regression check 1 — Hash divergence: capture `_metadata.json` `hash` (and `browserHash` if used) before and after the fix; equality across an upgrade means the cache was reused and the fix did not actually change behavior.
- 02Regression check 2 — Byte-level chunk comparison: fetch the pre-bundled chunk served by the dev server after the fix and compare it to a chunk fetched after a fresh cold cache; equality proves the warm serve path is producing the post-upgrade transform, not the pre-upgrade one.
- 03Regression check 3 — Symbol-level sanity check: run `node -e` against the served chunk (paste the chunk body or import it via the package's main entry in a Node script that does not execute the dev server URL) to confirm that exports referenced by application code resolve to defined values instead of undefined, indicating the cached transform now matches the installed source.
- 04Regression check 4 — HMR-bypass reload: perform at least three full browser reloads (not HMR-only updates) that each trigger imports of the upgraded dependency, and confirm the served chunk content is stable and matches the post-upgrade source across all three reloads.
- 05Regression check 5 — Log re-optimization signal: enable Vite's verbose logging on a second verification run and confirm that startup logs list the upgraded dependency inside the re-optimized set; absence of the entry means the dependency was excluded from pre-bundling and the original symptom cannot recur in the same form.
Prevention and next steps
- •Treat `node_modules/.vite` as part of the dependency surface: add it to the documented cache-invalidation checklist alongside `package.json`, the lockfile, and the Vite version so upgrades automatically trigger a cold pre-bundle pass.
- •Keep `optimizeDeps.include` explicit and minimal for dependencies that frequently change their `exports` map, so Vite's discovery does not depend on heuristics that drift between versions.
- •Run a CI step that records the `_metadata.json` `hash` after each dependency upgrade PR, so a missing hash delta in a release run is surfaced as a review comment rather than a production symptom.
- •Document the expected sequence (stop server, delete cache directory, restart, reload) in team runbooks so that pre-bundle invalidation is performed consistently after every Vite or dependency upgrade.
Safe commands and checks
vite --version cat node_modules/vite/package.json | grep '"version"' cat node_modules/.vite/deps/_metadata.json | head -n 50 cat node_modules/<dep>/package.json | grep '"version"' grep -A 5 '"<dep>"' <lockfile> ls -la node_modules/.vite/deps | head -n 50 Stop the dev server, rename the project-local Vite cache directory to <cache-backup>, restart Vite, and compare the old and new metadata hashes.