vite-mastery

12.7 · difficulty 3/4 · 18 min read

Troubleshooting Cookbook

A systematic guide for Vite failures: stalled dev servers, broken HMR, blank production pages, dynamic chunk 404s, stale optimized deps, and Node module externalization.

Vite 8.1Stable

Classify First

StageSymptomFirst tool
CLI/configstartup or config failureterminal stack
dev serverpending requests, blank pageNetwork + --debug
HMRchanges ignored or full reloadsDEBUG=vite:hmr
depsslow startup, repeated optimizationDEBUG=vite:deps + --force
buildblank production, chunk 404pnpm build + pnpm preview
pluginsslow transform or wrong orderDevTools / inspect

Do not start by deleting node_modules. First identify the stage, or you may erase the evidence.

0. Minimal Reproduction Info

Collect:

bash
node -v
pnpm -v
pnpm vite --version
pnpm vite --debug

Also note the OS, package manager, lockfile state, monorepo/WSL/Docker/remote-container usage, HTTPS/proxy/port-forwarding, and whether dev or build fails.

1. Dev Server Requests Stay Pending

Check file descriptor limits, inotify limits, browser tabs, and custom middleware. Then run:

bash
pnpm vite --debug

Common middleware bug:

ts
configureServer(server) {
  server.middlewares.use((req, res, next) => {
    if (req.url?.startsWith("/api")) {
      res.end("ok")
      return
    }
    // missing next()
  })
}

Fix:

ts
configureServer(server) {
  server.middlewares.use((req, res, next) => {
    if (req.url?.startsWith("/api")) {
      res.end("ok")
      return
    }
    next()
  })
}

2. HMR Detects Changes but Does Not Update

bash
DEBUG=vite:hmr pnpm dev
CauseHow to tellFix
no HMR boundaryfull reload logsadd import.meta.hot.accept or framework plugin
circular dependencydebug log shows pathbreak the cycle
custom hook returns wrong modulesafter adding handleHotUpdatereturn correct modules or full reload
watcher does not fireno terminal log after savecheck WSL/Docker/network drive

Do not return an empty array casually from handleHotUpdate; it means no module should be updated.

3. Dev Works, Build Is Blank

Always reproduce production locally:

bash
pnpm build
pnpm preview

Then check console errors, JS/CSS/chunk 404s, base, uploaded assets/, HTML cache, and browser extensions.

If the app is deployed under /app/, configure:

ts
export default defineConfig({
  base: "/app/",
})

Long-cached HTML is another common cause. Old HTML points to chunks that no longer exist.

4. Dynamic Import Fails

text
Failed to fetch dynamically imported module

Use the Network panel:

  • Is the URL missing the deployment sub-path?
  • Does the chunk exist on the server?
  • Is the response actually an HTML 404 page?
  • Is a Service Worker caching old files?
  • Did the CDN update HTML but not assets?

For broad dynamic imports, prefer import.meta.glob:

ts
const pages = import.meta.glob("./pages/*.tsx")
const page = pages[`./pages/${name}.tsx`]

5. Optimized Dependencies Are Stale

Symptoms: startup refresh, linked package stale, or dependencies optimize every run.

bash
DEBUG=vite:deps pnpm dev
pnpm vite --force

If it keeps happening, fix the cause:

CauseFix
dynamic import dependency missedoptimizeDeps.include
workspace package optimized as dependencyoptimizeDeps.exclude or alias to source
lockfile and node_modules mismatchreinstall
patches or overrides change oftenstabilize overrides

6. Node Module Externalized in Browser

text
Module "fs" has been externalized for browser compatibility

This means browser code imported a Node built-in. Vite does not automatically polyfill fs, path, or crypto.

Check whether the code should run in the browser, split server-only code, replace it with Web APIs, or choose a browser-compatible package. Avoid adding broad polyfills unless you understand the bundle cost.

7. Plugin Missing or Slow

For missing plugins, check plugins, apply, enforce, filters, and whether the hook only runs in build.

For slow transforms:

  • Use Vite DevTools for plugin timing
  • Use vite-plugin-inspect for a single module transform chain
  • Add timing to resolveId, load, and transform

Most plugin performance bugs come from overly broad filters, not a single slow line.

8. ESM/CJS Config Problems

Prefer ESM config:

ts
import { defineConfig } from "vite"

export default defineConfig({})

If an ESM-only package is loaded through require(), use ESM syntax, set "type": "module" if appropriate, upgrade old plugins, and avoid mixing require() with ESM-only dependencies in Vite config.

text
1. Reproduce: dev, build, or preview?
2. Classify: config / dev server / HMR / deps / build / plugin
3. Enable the matching debug logs
4. Narrow to one module, plugin, chunk, or request
5. Make one minimal fix
6. Verify with build + preview

Change one variable at a time.

Check Yourself

  1. Why start HMR debugging with DEBUG=vite:hmr?
  2. How do you decide if a dynamic import failure is code or deployment?
  3. What does --force fix, and why should it not be permanent?
  4. What is the right order for browser-side Node externalization warnings?
bash
# A project works in dev but sometimes blanks after deployment.
# Console shows Failed to fetch dynamically imported module.
# CDN has long cache enabled.
# Write your debugging order and likely fix.