localhost:5173 / vite-hmr

Hot Module Replacement on localhost:5173

HMR swaps a changed module in the running page over a WebSocket instead of reloading it; when that WebSocket cannot connect, Vite falls back to full page reloads or stops updating at all.

What actually happens

Vite watches your files. On a change it sends the new module over a WebSocket to the browser, where it replaces the old one in place — component state survives, scroll position survives, the page does not reload. A full reload means that handoff failed somewhere.

The deprecated option almost every answer still shows

The WebSocket settings under server.hmr — protocol, host, port, path, clientPort, timeout, server — are deprecated. They live under server.ws now. The old keys are still synced automatically so existing configs keep working, which is exactly why the outdated advice never stopped circulating.

vite.config.js
// deprecated
server: { hmr: { clientPort: 443 } }

// current
server: { ws: { clientPort: 443 } }

Behind a reverse proxy or tunnel

The browser loads the page from your public hostname on port 443, then tries to open the WebSocket on the port Vite thinks it is on. clientPort tells the client where to connect instead.

vite.config.js
export default defineConfig({
  server: {
    ws: {
      protocol: 'wss',
      clientPort: 443,
    },
  },
})

The fallback message in the console

If the proxy in front of Vite does not forward WebSocket connections, the client gives up on the proxied route and connects to the HMR server directly. It prints a connection error first and then works anyway.

console
Direct websocket connection fallback. Check out https://vite.dev/config/server-options.html#server-ws to remove the previous connection error.

server.hmr today

What remains under server.hmr is the behaviour itself: false turns HMR off, and overlay: false hides the full-screen error overlay without touching the updates.

vite.config.js
server: {
  hmr: { overlay: false },
}

Nothing updates at all

Two usual causes, both outside Vite. On a bind-mounted volume in Docker or on some network drives, filesystem events never reach the watcher — polling is the workaround. And a module that does not accept an update propagates upward until something does; if nothing does, Vite reloads the page. That is not a bug, that is the fallback working.

vite.config.js
server: {
  watch: { usePolling: true },   // only when events do not arrive
}

Turning it off is a cost

usePolling asks the filesystem for changes on a timer instead of being told. On a large project it keeps a core busy. Use it where events genuinely do not arrive, not as a default.

# faq

Questions

What does HMR stand for?

Hot Module Replacement — replacing a changed module in the running page without reloading it.

Why does my page do a full reload instead of hot updating?

Either the WebSocket did not connect, or no module in the chain accepted the update, so Vite fell back to reloading.

Is server.hmr.clientPort still correct?

It still works but is deprecated. The current location is server.ws.clientPort.

Why does HMR not work in Docker?

File change events often do not cross a bind mount. server.watch.usePolling is the usual workaround, at the cost of CPU.

# next

Related

# sources

Checked against the Vite documentation on 2026-09-11.