Diagnosing Vite HMR Connection Failures and Update Stalls
Diagnose Vite HMR WebSocket failures, port mismatches, and proxy issues with DevTools checks and config fixes.
28 May 2026, 03:25 UTC

Problem: edits save but the browser does not update
You save a component file, the Vite terminal prints the change, yet the browser stays stale. In other cases, DevTools shows a WebSocket connection that fails or stays pending while the dev server keeps running. This is usually a Hot Module Replacement (HMR) transport problem, not a code error.
HMR is a Vite dev-server feature that patches changed modules in the running page without a full browser reload. It depends on a WebSocket connection from the browser to the Vite dev server, usually on the same port as the HTTP server unless server.port or server.hmr.port is overridden.
Useful takeaway: confirm the WebSocket in DevTools first, then align the launch command, port, proxy headers, and dependency configuration. HMR runs under vite or vite dev; it does not run under vite preview.
Recognizable condition and likely cause
| Recognizable condition | Most likely cause | First place to check |
|---|---|---|
| Whole page reloads on every save | HMR inactive; server started with vite preview or a production build | Terminal command used to start the server |
| WebSocket shows failed, pending, or upgrade errors | Port mismatch or reverse proxy not forwarding WebSocket upgrade headers | DevTools Network tab, filtered to WS |
| Server logs the file change, browser stays silent | Module excluded from optimizeDeps.exclude or non-ESM entry point | vite.config.js |
| No runtime error overlay appears | server.hmr.overlay set to false | vite.config.js |
Ordered checks
1. Confirm you are running the development server
Run the dev server from the project root in a terminal. No special permissions are required.
vite
# or
vite dev
Expected check: the terminal prints a local URL such as http://localhost:5173. Risk: if the port is already in use, Vite picks another port or exits; restart after freeing the port.
Do not use vite preview for this check. It serves the production build for local inspection and does not enable HMR.
2. Inspect the WebSocket connection
Open browser DevTools, go to the Network tab, and filter by WS. Reload the page while the dev server is running.
Expected check: a WebSocket connection to the dev server origin opens successfully. If the row shows failed, closed, or pending, note the host and port in the WebSocket URL and proceed to the port check.
3. Compare HTTP and HMR ports
Open vite.config.js. server.port is the HTTP port; server.hmr.port is the WebSocket port. By default they match. If you override one but not the other, the browser may attempt HMR on the wrong port.
If a reverse proxy sits between the browser and Vite, the proxy must forward Upgrade: websocket and Connection: upgrade headers. Without those headers, the WebSocket upgrade is rejected.
Risk: changing server.port or server.hmr.port requires restarting the dev server. Absolute URLs in your application may break if they hardcode the old port.
4. Confirm the entry point and dependency exclusions
Check that your HTML entry uses a module script, for example <script type='module'>, or the equivalent provided by your framework plugin. Review optimizeDeps.exclude in vite.config.js. Broad exclusions prevent Vite from pre-bundling and tracking modules through its HMR boundary unless the module itself handles import.meta.hot.
5. Check the runtime error overlay
server.hmr.overlay controls whether Vite shows an in-browser overlay for HMR runtime errors. Setting it to false suppresses the overlay without stopping updates, which can make failures look silent.
Fixes tied to findings
Fix: wrong launch command
Stop vite preview and restart with vite dev. HMR is not available in preview mode.
Fix: port mismatch
Set server.hmr.port to the same value as server.port, or remove the override so both default to the same port. Restart the dev server after editing vite.config.js.
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 5173,
hmr: {
port: 5173,
},
},
})
Fix: proxy not forwarding WebSocket headers
If your config proxies API requests, add ws: true to the proxy rule so WebSocket upgrade requests are forwarded. This example assumes a local API on port 3000.
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
ws: true,
},
},
},
})
Expected check: after restart, the DevTools WebSocket row shows an open connection. Risk: ws: true forwards all WebSocket traffic under that path; ensure you do not expose internal sockets unintentionally.
Fix: silent runtime errors
Set server.hmr.overlay to true or remove the line so Vite shows runtime error diagnostics during HMR.
Limitations
- HMR is a development-only feature.
vite buildandvite previewdo not enable HMR by default. - Behavior differs between Vite major versions and across framework plugins such as React, Vue, or Svelte. Verify against your installed Vite version.
- This guide covers local development. It does not cover SSR HMR or production environments.
Practical verification
Edit any component file and save. The DevTools Console should show an HMR update notice, and the page should not perform a full reload. If the WebSocket tab shows no errors and the change appears, the transport is working.
Escalation criteria
- WebSocket connects but updates still stall across multiple files and framework plugins: capture your Vite version, plugin versions, and a minimal reproduction before escalating to Vite or the plugin maintainers.
- HMR works in an incognito window but not your default profile: a browser extension is likely intercepting the WebSocket or forcing reloads.
- Failure occurs only on a VPN or corporate network: a firewall may be blocking WebSocket traffic; contact your network team.