.claude/skills/vite/vite-errors/vite-errors-dev-server/SKILL.md
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer vite-errors-dev-serverInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Symptom | Cause | Fix |
|---------|-------|-----|
| Full page reload instead of HMR | No HMR boundary — module lacks import.meta.hot.accept() | Add import.meta.hot.accept() in the changed module or a parent importer |
| HMR accept not detected | Whitespace before ( in import.meta.hot.accept( | ALWAYS write import.meta.hot.accept( with NO line break between accept and ( |
| WebSocket connection failed | Wrong HMR config behind reverse proxy | Set server.hmr.clientPort, server.hmr.host, server.hmr.protocol |
| EADDRINUSE on startup | Port already in use | Kill the process on that port, or set server.strictPort: false (default) for auto-fallback |
| Proxy returns 500/ECONNREFUSED | Wrong target format or backend not running | Verify target URL includes protocol (http://), add changeOrigin: true |
| CORS blocked request | Default server.cors allows only localhost origins | Set server.cors: true for any origin, or configure specific origin |
| "Outside of Vite serving allow list" | server.fs.strict blocks files outside workspace root | Add the directory to server.fs.allow |
| HTTPS certificate errors | Missing or invalid cert/key files | Pass valid key and cert to server.https, or use @vitejs/plugin-basic-ssl |
| Module not found / alias not working | Alias path uses wrong format | Use absolute paths with path.resolve() or /src/... root-relative format |
| File changes not detected | Chokidar fails in Docker/WSL2 | Set server.watch.usePolling: true |
| 403 Forbidden on all requests | server.allowedHosts blocks the hostname | Add the hostname to server.allowedHosts array, or set to true |
| Slow initial page load | Modules transformed on-demand on first request | Configure server.warmup.clientFiles with frequently used entry files |
| Middleware mode errors | Missing appType: 'custom' | ALWAYS set appType: 'custom' when using server.middlewareMode: true |
| Browser errors not in terminal | server.forwardConsole not enabled | Set server.forwardConsole: true (v8+ only, auto-enabled for AI agents) |
When a file change triggers a full page reload instead of an HMR update, follow this decision tree:
File changed
|
v
Does the module call import.meta.hot.accept()?
|
+-- NO --> Does any PARENT module accept it?
| |
| +-- NO --> FULL RELOAD (no HMR boundary exists)
| | FIX: Add import.meta.hot.accept() to the module
| | or to a parent that imports it
| |
| +-- YES --> HMR update at the parent boundary
|
+-- YES --> Is accept() detected by static analysis?
|
+-- NO --> Check: is there a line break between
| "accept" and "(" ?
| FIX: Write import.meta.hot.accept( on one token
|
+-- YES --> Does the accept callback succeed?
|
+-- NO --> Does it call hot.invalidate()?
| |
| +-- YES --> Propagates up; if no
| | parent boundary: FULL RELOAD
| +-- NO --> Error in callback: FULL RELOAD
|
+-- YES --> HMR UPDATE (success)
ALWAYS wrap HMR code in if (import.meta.hot) { ... } — this guard is required for production tree-shaking.
ALWAYS write import.meta.hot.accept( as a single token — Vite uses static analysis to detect HMR boundaries. A line break between accept and ( causes the boundary to go undetected, resulting in full reloads.
// CORRECT - detected as HMR boundary
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// handle update
})
}
// WRONG - NOT detected as HMR boundary
if (import.meta.hot) {
import.meta.hot.accept
((newModule) => {
// handle update
})
}
NEVER reassign import.meta.hot.data — mutate its properties instead:
// CORRECT
import.meta.hot.data.count = 42
// WRONG - reassignment is NOT supported
import.meta.hot.data = { count: 42 }
When the HMR WebSocket fails to connect (common behind reverse proxies, Docker, or non-standard setups):
// vite.config.ts
export default defineConfig({
server: {
hmr: {
protocol: 'ws', // 'ws' or 'wss' for HTTPS
host: 'localhost', // Client-visible hostname
port: 5173, // Server-side WebSocket port
clientPort: 443, // Port the CLIENT connects to (proxy port)
},
},
})
ALWAYS set clientPort when a reverse proxy terminates SSL or maps ports. The client needs to know the external port, not the internal one.
NEVER assume WebSocket auto-detection works behind proxies — explicitly configure server.hmr when using nginx, Caddy, or cloud-based dev environments.
// WRONG: missing protocol in target
server: {
proxy: {
'/api': {
target: 'localhost:3000', // WRONG - needs http://
},
},
}
// CORRECT
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
ALWAYS include the protocol (http:// or https://) in proxy targets.
ALWAYS set changeOrigin: true when proxying to a different hostname — this sets the Host header to match the target.
server: {
proxy: {
'/socket.io': {
target: 'ws://localhost:5174',
ws: true, // REQUIRED for WebSocket proxying
},
},
}
ALWAYS set ws: true explicitly when proxying WebSocket connections.
Default server.cors allows only localhost origins (pattern: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/).
// Allow all origins (development only)
server: {
cors: true,
}
// Allow specific origin (backend integration)
server: {
cors: {
origin: 'http://my-backend.example.com',
},
}
NEVER set cors: true in production preview servers without understanding the security implications.
Error: "The request url is outside of Vite serving allow list"
// FIX: Add the directory to the allow list
server: {
fs: {
allow: [
// Workspace root (auto-included)
searchForWorkspaceRoot(process.cwd()),
// Additional directories
'/path/to/shared/packages',
'../sibling-project/src',
],
},
}
ALWAYS use searchForWorkspaceRoot() from vite to auto-detect the workspace root in monorepos.
NEVER set server.fs.strict: false to work around this error — it disables an important security boundary. Add specific paths to server.fs.allow instead.
server: {
port: 5173,
strictPort: false, // DEFAULT: tries next port if 5173 is taken
}
strictPort: false (default): Vite silently increments to the next available portstrictPort: true: Vite exits with EADDRINUSE if the port is occupiedALWAYS set strictPort: true in CI/CD environments where port predictability matters.
Chokidar's native file watching does NOT work reliably on network filesystems, Docker volumes, or WSL2 cross-filesystem mounts:
server: {
watch: {
usePolling: true, // REQUIRED for Docker/WSL2
interval: 1000, // Poll every 1s (reduce CPU; default 100ms)
},
}
ALWAYS enable usePolling when running Vite inside Docker or when source files are on a different filesystem (WSL2 accessing Windows files or vice versa).
ALWAYS set appType: 'custom' together with server.middlewareMode: true:
import express from 'express'
import { createServer as createViteServer } from 'vite'
const app = express()
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom', // REQUIRED - disables built-in HTML handling
})
app.use(vite.middlewares)
NEVER omit appType: 'custom' when using middleware mode — Vite's default SPA fallback middleware will interfere with your custom server routing.
import path from 'node:path'
export default defineConfig({
resolve: {
alias: {
// CORRECT: absolute path
'@': path.resolve(import.meta.dirname, './src'),
// CORRECT: root-relative (Vite resolves from project root)
'@components': '/src/components',
// WRONG: relative path without resolve
'@': './src',
},
},
})
ALWAYS use path.resolve() or root-relative paths (/src/...) for aliases. Relative paths like ./src resolve from the config file location, which may not be what you expect.
Vite resolves these extensions by default: .mjs, .js, .mts, .ts, .jsx, .tsx, .json.
NEVER add .vue, .svelte, or other framework extensions to resolve.extensions — framework plugins handle those. Adding them causes double-resolution issues.
Since Vite 6.2+, the dev server validates the Host header to prevent DNS rebinding attacks:
server: {
allowedHosts: ['my-app.dev.local', '.example.com'], // dot prefix = subdomains
}
// Or disable protection entirely (development only)
server: {
allowedHosts: true,
}
ALWAYS add custom dev domains to allowedHosts when using tools like ngrok, localtunnel, or custom /etc/hosts entries.
Use server.warmup to pre-transform frequently needed modules:
server: {
warmup: {
clientFiles: [
'./src/main.ts',
'./src/components/**/*.vue',
'./src/router/index.ts',
],
},
}
This transforms and caches these files BEFORE the browser requests them, eliminating the cold-start waterfall on first page load.
Forward browser runtime errors and console output to the terminal:
server: {
forwardConsole: true, // Auto-detected for AI agents in v8+
}
NEVER rely on server.forwardConsole in Vite 6.x or 7.x — this feature was introduced in Vite 8.x.
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.
tools
Use when configuring SSR environments, edge workers, custom environments, or migrating to the Vite 6+ environment model. Prevents using the Environment API on Vite 5 (where it does not exist) and misconfiguring per-environment vs shared plugin scoping. Covers per-environment build and dev settings, EnvironmentOptions interface, custom environment providers, shared vs per-environment plugins, configuration inheritance, and migration from Vite 5 implicit environments. Keywords: vite, environment api, vite 6, SSR, edge workers, per-environment config, providers.