Vite build error: production build fails after dev server runs fine
Vite build error — production-only failures and rollup misalignment
On this page
What causes this error
Vite uses two different bundlers depending on phase. Dev runs on esbuild — fast, lenient, permissive about ambiguous module shapes. Production runs on Rollup — slower, stricter, unforgiving about anything that smells like a side effect or a circular dependency. The most demoralising production build errors are the ones the dev server hid for weeks because esbuild silently accepted what Rollup will not.
Four divergence patterns explain almost every report of this class. First: dead-code
elimination drops side-effect-only imports. A line like import 'reflect-metadata' is a
side-effect import — it has no exports the consumer references — so Rollup’s tree-shaker
removes it unless the package’s package.json declares "sideEffects" truthfully. esbuild
in dev mode preserves it; Rollup in prod drops it; the runtime crashes only after deploy.
Second: peer-dependency version skew. The dev tree resolved one version of a peer (because
the dev tool pulled it transitively); the production install resolves a different version
(because the lockfile was regenerated). Type definitions agree; runtime contracts diverge.
Third: dynamic-import chunking under Rollup splits code paths esbuild fused. A
React.lazy(() => import('./Heavy')) becomes a separate chunk in production; if
./Heavy had a top-level side effect the eager-imported code path relied on, the chunked
version executes that side effect later or not at all. Fourth: CommonJS interop differences —
esbuild flattens module.exports = { default: x } to a usable default import; Rollup’s
commonjs plugin requires explicit configuration in vite.config.ts optimizeDeps.include
or the dynamic shape leaks through.
How to fix it manually
Run pnpm build --debug and read the Rollup warnings before the error — the warnings about
circular dependencies and sideEffects field misconfiguration are the diagnostic signal. Run
pnpm exec vite build --mode production --logLevel info to expose the chunk graph. If the
issue is sideEffects, edit the offending package’s consumer to import the symbol that
matters (or, if it is your own package, set "sideEffects": false or list the side-effect
files explicitly). For peer-dep skew, run pnpm why <package> and pin the version
intentionally in the consumer’s package.json.
Copy this prompt into your AI coding agent
# Goal
Fix this Vite production build error using the smallest safe change.
Diagnose which of four divergence patterns applies (sideEffects drop /
peer-dep skew / dynamic-import chunking / CommonJS interop) before patching.
Production uses Rollup; dev uses esbuild. The fix lives in the Rollup-side
behaviour, not in the source the dev server already accepts.
# Context
The full build output:
<paste pnpm build output including any Rollup warnings BEFORE the error line>
The exact failure mode: <build error / runtime error after build / chunk-loading error>
The package at issue (if narrowed): <package name>
The pnpm why <package> output:
<paste here>
# In-scope
- vite.config.ts optimizeDeps.include and optimizeDeps.exclude
- vite.config.ts build.rollupOptions
- package.json sideEffects declaration (in your own packages)
- package.json dependency version pin (for peer-dep skew)
- The single failing import or chunk-boundary
# Out-of-scope
- Switching from Vite to Webpack or another bundler
- Disabling tree-shaking globally to mask the problem
- Modifying upstream third-party package source
- Renaming components or restructuring routes
# Verification
Run `pnpm build` and confirm exit code 0.
Run `pnpm preview` and reproduce the originally failing path manually.
Run `pnpm exec vite build --mode production --logLevel info` and grep for
"sideEffects" warnings — there should be zero remaining.
# Output format
1. One sentence: which of the four divergence patterns this is.
2. The diff (only changed config + package.json).
3. The verification output, last 15 lines. Why this prompt works
The four-divergence framing inoculates against the most common Claude Code response to
production build errors, which is “rebuild from scratch with different config”. Naming
vite.config.ts optimizeDeps and build.rollupOptions in In-scope and excluding “switching
bundlers” in Out-of-scope keeps the fix scoped to the Rollup-side behaviour where the
divergence actually lives. The verification step explicitly greps for residual sideEffects
warnings because Rollup logs them as warnings rather than errors — the build can succeed with
warnings present and silently regress on the next deploy.
Variants by symptom
| Symptom | Diagnostic command |
|---|---|
| Build succeeds, runtime error in prod only | Inspect dist/assets/ chunk graph |
[plugin:vite:resolve] sideEffects warning | Edit consumer’s package.json sideEffects |
| Peer-dep version skew | pnpm why <package> and pin the version |
| Dynamic-import chunk fails to load | Check build.rollupOptions.output.manualChunks |
| CommonJS package not interop’d | Add to optimizeDeps.include |
Related errors and tools
Frequently asked questions
Why does my build fail when the dev server works fine?
Dev runs on esbuild, which is lenient, while production runs on Rollup, which is strict. Rollup drops side-effect-only imports and enforces stricter module interop that esbuild silently accepted.
How do I find which import Rollup dropped?
Run pnpm build --debug and read the Rollup warnings about sideEffects and circular dependencies that print just before the error line.
Generate a fix prompt
Build a tailored AI fix prompt for this error.