Vite Build Fix 8 min read

Vite Build Error? Fix Chunk Size Warnings, Module Errors, and More

Troubleshoot the most common vite build failures — chunk size exceeded, Cannot find module, missing env vars, broken aliases, and Vite 4→5 migration breaks.

Common errors and fixes

1. Chunk size exceeds limit warning

The build succeeds but prints a warning in yellow. Vite's default chunkSizeWarningLimit is 500 KB. Large vendor bundles (React, lodash, date-fns) are the usual culprit. The real fix is to split them out, not just silence the warning.

(!) Some chunks are larger than 500 kB after minification.
dist/assets/index-Cba3kL9r.js  892.34 kB

Option A — manualChunks (recommended): split node_modules into a separate vendor chunk so the browser can cache it independently:

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return 'vendor';
          }
        }
      }
    }
  }
};

Option B — dynamic imports: for route-level code splitting, use import() instead of static import so each route becomes its own chunk:

// Before (puts everything in main chunk)
import HeavyChart from './HeavyChart';

// After (lazy-loaded separate chunk)
const HeavyChart = lazy(() => import('./HeavyChart'));

Option C — raise the warning limit (only if code splitting is not possible):

// vite.config.js — suppresses the warning, does not fix the bundle size
export default {
  build: {
    chunkSizeWarningLimit: 1000  // KB
  }
};

2. Cannot find module in build but not in dev

vite dev uses Node resolution at serve time; vite build uses Rollup which resolves modules at bundle time with different rules. The most common cause: path aliases in tsconfig.json that are not mirrored in vite.config.js.

// tsconfig.json — only affects TypeScript type checking
{
  "compilerOptions": {
    "paths": {
      "@components/*": ["./src/components/*"]
    }
  }
}

// vite.config.js — MUST mirror every alias from tsconfig for the build
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@components': path.resolve(__dirname, './src/components')
    }
  }
});
Asset paths: do not reference assets with new URL('./image.png', window.location.href) — use new URL('./image.png', import.meta.url) so Rollup can resolve and hash the asset path at build time.
  • CommonJS packages: packages that export only require() need @rollup/plugin-commonjs — Vite includes it by default for optimizeDeps but some edge cases still require explicit configuration.
  • Peer dependency conflicts: if npm install fails with ERESOLVE, use npm install --legacy-peer-deps. With pnpm, add shamefully-hoist=true to .npmrc.

3. Environment variables undefined after build

Vite statically replaces import.meta.env.VITE_* at build time. Variables without the VITE_ prefix are intentionally omitted from the client bundle. process.env does not exist in the browser and will throw at runtime.

# .env — variables without VITE_ prefix are server-only (safe, never bundled)
DATABASE_URL=postgres://user:pass@host/db
SECRET_KEY=my-secret

# .env — variables WITH VITE_ prefix are inlined into the browser bundle
VITE_API_URL=https://api.example.com
VITE_PUBLIC_KEY=pk_live_abc123
// WRONG — process.env is undefined in the browser after build
const apiUrl = process.env.API_URL;

// CORRECT
const apiUrl = import.meta.env.VITE_API_URL;
  • Mode files: .env.production is loaded for vite build, .env.development for vite dev — ensure the variable exists in the right file.
  • SSR builds: in SSR mode, process.env works on the server side only — do not use it in code that runs in both environments.

4. Base path config for non-root deployments

If your app deploys to a sub-path like https://example.com/app/, asset URLs in dist/ will point to /assets/index.js instead of /app/assets/index.js and the page will load blank. Set base in your config:

// vite.config.js
export default defineConfig({
  base: '/app/',  // trailing slash required
});
  • GitHub Pages: set base: '/repo-name/' — GitHub Pages serves projects at username.github.io/repo-name/ by default.
  • Access in code: use import.meta.env.BASE_URL inside your app to construct absolute links that respect the base setting.

5. Vite 4 → 5 breaking changes

Upgrading from Vite 4 to Vite 5 can break builds silently. The most impactful changes:

  • resolve.conditions: the browser condition is now always included for non-SSR builds. Some packages that relied on the absence of this condition will resolve to their browser build unexpectedly — set resolve.conditions: [] to restore old behavior.
  • Worker format: Web Workers now default to { format: 'es' }. If your target environment does not support ES module workers, add worker: { format: 'iife' } to your Vite config.
  • Node 18+ required: Vite 5 dropped support for Node 14 and 16. If your CI pipeline pins an older Node version, the build will fail at startup with an unclear error.
  • CJS server API removed: the CommonJS build of Vite's server API (require('vite')) was removed — programmatic usage must use ESM (import { createServer } from 'vite').
# Check which Vite version you are running
npx vite --version

# Upgrade to latest Vite 5
npm install vite@latest --save-dev
🔔

Know when your build infrastructure has an outage

Monitor npm, Vercel, Netlify, and GitHub Actions — free email alerts, no credit card.

FAQ

How do I fix the Vite chunk size warning?

Add manualChunks in build.rollupOptions.output to split node_modules into a vendor chunk, or use dynamic import() for route-level code splitting. As a last resort, raise build.chunkSizeWarningLimit.

Why does "Cannot find module" appear in vite build but not vite dev?

Path aliases in tsconfig.json must be mirrored in vite.config.js resolve.alias — Rollup does not read tsconfig.json. Also use import.meta.url for asset references instead of __dirname.

Why are my environment variables undefined after vite build?

Vite only exposes variables with the VITE_ prefix to the browser. Rename your variable to VITE_MY_VAR and access it as import.meta.env.VITE_MY_VAR — never use process.env in client-side code.

What changed from Vite 4 to Vite 5 that breaks builds?

Key breaking changes: resolve.conditions defaults changed; Worker format defaults to ES modules; Node.js 14/16 no longer supported; CommonJS server API removed. Check the Vite 5 migration guide and ensure Node 18+ in your CI environment.

Monitor related services