Dependency pre-bundling

Vite · Advanced · 12 min

Most 'works after restart' and 'duplicate React' dev issues trace back to dependency optimization.

On the first dev-server start, Vite scans your source for bare imports (import { debounce } from "lodash-es") and pre-bundles those dependencies (using Rolldown in Vite 8) into node_modules/.vite/deps. Two reasons:

  • CommonJS and UMD to ESM: many packages still ship CommonJS, which browsers cannot import natively.
  • Fewer requests: a package made of hundreds of internal modules becomes one file, avoiding request waterfalls.

The browser then receives cached, strongly cached (max-age=31536000,immutable) files, and Vite rewrites your imports to point at them.

The cache is invalidated when the lockfile, the relevant config or patchedDependencies change. Force a rebuild with vite --force, or delete node_modules/.vite.

When something goes wrong:

  • Dependency discovered late: an import that only appears after navigation (e.g. inside a lazily loaded route) triggers re-optimization and a page reload. Add it to optimizeDeps.include.
  • Linked monorepo packages are treated as source, not pre-bundled. If such a package ships CommonJS, add it to include.
  • Large ESM-only packages that are already browser-ready can go in optimizeDeps.exclude.
  • Duplicated singletons (two copies of React) usually need resolve.dedupe: ["react", "react-dom"].

Pre-bundling applies only to the dev server; production builds bundle dependencies along with your code.

Example

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: {
    include: [
      "lodash-es",          // discovered late (only in a lazy route)
      "@acme/legacy-utils", // linked workspace package that ships CommonJS
    ],
    exclude: ["@acme/esm-widgets"], // already ESM, keep as source
  },
  resolve: {
    dedupe: ["react", "react-dom"], // one copy even if several are installed
  },
});

// $ npx vite --force          # rebuild the dependency cache
// $ ls node_modules/.vite/deps # pre-bundled files + _metadata.json

Remember

  • Pre-bundling converts CommonJS to ESM and collapses many files
  • Cache lives in node_modules/.vite; --force rebuilds it
  • include late or linked CJS deps; exclude browser-ready ESM
  • Use resolve.dedupe for duplicated singletons

Watch out for

  • Repeatedly deleting node_modules instead of adding a late-discovered dependency to optimizeDeps.include.

Check yourself

  1. Navigating to a lazy route causes 'new dependencies optimized, reloading'. What is the fix?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vite.dev/guide/dep-pre-bundling