How does Vite's HMR system work?
How does Vite's HMR system work?
About vitejs/vite · 2 messages · last activity 8/1/2026
Based on the retrieved source, Vite's HMR (Hot Module Replacement) system works through a client/server WebSocket protocol that dispatches typed payloads to handle different kinds of updates. Here's the flow:
1. Connection setup
The browser client establishes a WebSocket connection to the dev server, configured from injected build-time constants (__HMR_PROTOCOL__, __HMR_HOSTNAME__, __HMR_PORT__, etc.) [packages/vite/src/client/client.ts:L18-L38]. It creates a ModuleRunnerTransport over WebSocket with a fallback mechanism: if the inferred port fails to connect, it retries against a "direct" socket target [packages/vite/src/client/client.ts:L52-L80].
2. Message handling via a typed payload switch
The core of HMR is a handleMessage function that switches on payload.type [packages/vite/src/client/client.ts:L206-L245]. The payload types include:
connected— just logs that the client connected [packages/vite/src/client/client.ts:L207-L209].update— the main HMR case, described below.custom— dispatches custom events to listeners, e.g. used for console forwarding and reconnect-polling logic [packages/vite/src/client/client.ts:L275-L290].full-reload— triggers a full page reload, with special-casing for HTML file edits (only reloads if the browser is currently on that page) [packages/vite/src/client/client.ts:L292-L310].prune— removes stale modules viahmrClient.prunePaths[packages/vite/src/client/client.ts:L311-L313].error— shows an error overlay or logs to console [packages/vite/src/client/client.ts:L314-L318].
3. Update handling: JS vs CSS
For an update payload, it first calls hmrClient.notifyListeners('vite:beforeUpdate', payload), then handles a special edge case: if this is the very first update and an error overlay is already showing (meaning the page loaded with a server compile error), it forces a full location.reload() instead of trying to hot-patch [packages/vite/src/client/client.ts:L210-L219].
Each update in payload.updates is processed based on type:
js-update— delegated tohmrClient.queueUpdate(update)[packages/vite/src/client/client.ts:L228-L230].css-update— handled directly in the client by finding the matching<link>tag, cloning it with a new cache-busted URL, and swapping it in only after the new stylesheet loads (avoiding a Flash of Unstyled Content), then removing the old tag [packages/vite/src/client/client.ts:L232-L273].
After processing, vite:afterUpdate listeners are notified [packages/vite/src/client/client.ts:L275].
4. Module Runner (SSR/non-browser) variant
For non-browser environments, createHMRHandlerForRunner wraps the same createHMRHandler shared logic but adapts behavior for the ModuleRunner [packages/vite/src/module-runner/hmrHandler.ts:L7-L88]:
js-updatepaths are unwrapped viaunwrapIdsince the runner caches modules by full path without the/@id/prefix [packages/vite/src/module-runner/hmrHandler.ts:L18-L21].css-updateisn't supported — it just logs an error, since CSS HMR requires a DOM/<link>tag [packages/vite/src/module-runner/hmrHandler.ts:L23].full-reloadre-imports all "entrypoint" modules after clearingrunner.evaluatedModules, rather than reloading a browser page [packages/vite/src/module-runner/hmrHandler.ts:L31-L54].
5. Console forwarding
A related plugin, forwardConsolePlugin, listens on the server side for vite:forward-console custom HMR events per-environment and re-logs them (or formats them as errors) through the Vite logger — this lets console output/errors from a module runner environment be surfaced in the terminal running the dev server [packages/vite/src/node/plugins/forwardConsole.ts:L11-L53].
Summary
The system is built around a shared HMRClient/createHMRHandler abstra
Want to ask your own question?
Open chat for vitejs/vitePublic Q&A. Generated by RepoPilot from the actual source of vitejs/vite. AI answers can be incomplete or stale — verify before relying on them.