← Debugging Guides

阅读中文版本

Vite optimize deps — Missing Exports and Reload Loops

Diagnose Vite dependency pre-bundling problems by clearing the cache, reading optimize debug output, and configuring include, exclude, and dedupe for linked packages.

intermediatedebuggingvitebundler

Symptoms

  • The requested module '/node_modules/.vite/deps/chunk-XXXX.js' does not provide an export named 'default'
  • The browser reloads repeatedly with new dependencies optimized
  • A workspace package resolves to a stale or duplicate copy after an edit
  • CommonJS dependencies fail only in dev, while the production build works

Possible causes

  • The pre-bundled cache is stale after a dependency upgrade or lockfile change
  • A CommonJS package is imported by name but never pre-bundled
  • A linked workspace package is pre-bundled and keeps its own copy of a shared framework
  • The dependency is discovered late through a dynamic import, triggering re-optimization

Diagnosis steps

Clear the pre-bundle cache and restart with force

rm -rf node_modules/.vite && npx vite --force

Interpretation

A clean start removes stale hashed chunks. If the error disappears, the cache was the trigger and the configuration still deserves review.

Read the optimizer debug output

npx vite --debug 2>&1 | grep -E 'optimiz|deps|reload' | head -40

Interpretation

The log lists which dependencies were discovered, bundled, and why a reload was triggered. A dependency appearing repeatedly points to late discovery.

Check how the import is resolved

npx vite --debug 2>&1 | grep -i 'lodash-es\|some-cjs-lib' | head -20

Interpretation

The resolved path shows whether the import comes from the workspace or node_modules and whether it is treated as an external or bundled dependency.

Inspect duplicate framework copies

npm ls react react-dom && ls node_modules/.vite/deps | head -20

Interpretation

Two React versions in the tree cause invalid hooks and duplicate context. The deps directory shows what was pre-bundled and under which hash.

Root cause

Vite pre-bundles dependencies into ESM files under node_modules/.vite so the browser does not request hundreds of modules. The cache is keyed by config, lockfile, and discovered entries; a change invalidates it, and a dependency discovered only at runtime forces a re-optimization and page reload. Linked packages that get bundled can carry their own framework copy, which breaks singleton assumptions and named exports.

Fix

Clear node_modules/.vite after dependency changes, add CommonJS or deep-import dependencies to optimizeDeps.include, exclude linked workspace packages so Vite serves their source directly, and dedupe shared frameworks. Use server.warmup to pre-transform entry points and avoid the first-load reload loop.

Risk note

Excluding a CommonJS package from optimization can make it fail in the browser because it is not valid ESM. Duplicating a framework through a linked package causes subtle runtime errors even when the build succeeds. Deleting the cache on every start hides configuration problems and slows development.

Verify the fix

Restart Vite, confirm the debug log shows one optimization pass with no subsequent reload, and verify the failing named import resolves in the browser. HMR should update the linked package without restarting the server.

Dev serves ESM, production bundles

Vite pre-bundles dependencies in development so the browser gets one ESM file per package instead of hundreds of requests. That cache lives under node_modules/.vite and is keyed by config and lockfile. When it is stale, imports point at old hashed chunks:

The requested module '/node_modules/.vite/deps/chunk-4F2K.js?v=9a1c'
does not provide an export named 'default'

Reset and observe

rm -rf node_modules/.vite
npx vite --force
npx vite --debug 2>&1 | grep -E "optimiz|deps|reload" | head -40

The debug output shows discovery, bundling, and reload events. A dependency that appears in a second optimization after startup was discovered late, usually through a dynamic import or a plugin transform.

Configure the optimizer

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    include: ['lodash-es', 'some-cjs-lib'],
    exclude: ['@acme/ui'],
  },
  resolve: {
    dedupe: ['react', 'react-dom'],
  },
  server: {
    warmup: {
      clientFiles: ['./src/main.tsx', './src/routes/*.tsx'],
    },
  },
});
  • include forces pre-bundling for dependencies Vite cannot discover statically.
  • exclude keeps a linked workspace package as source, so its edits hot-reload and it does not carry a second framework copy.
  • dedupe ensures one React instance across the app and linked packages.

Verify

Restart and confirm the optimizer runs once, the page does not reload in a loop, and the previously failing import resolves. Edit a file in the linked package and check HMR applies without a full restart. Finally, run the production build; dev-only optimizer fixes should not change runtime behavior, and a dependency that breaks only in build needs a different fix.