Knowing when HMR applies versus full reloads explains lost state and stale modules during development.
When you save a file, the dev server finds the changed module in its module graph and walks up the importers looking for an HMR boundary: a module that has declared it can accept updates. It then sends a message over a WebSocket, and the client re-imports the new version of the changed modules, without reloading the page.
- Framework plugins make components boundaries automatically: React Fast Refresh (
@vitejs/plugin-react), Vue, Svelte and Solid preserve component state across edits. - CSS updates are swapped in place.
- If no boundary is found up to the entry, Vite does a full page reload.
The `import.meta.hot` API lets plain modules opt in:
import.meta.hot.accept(cb): accept updates of this module;cbreceives the new module.import.meta.hot.dispose(cb): clean up side effects (timers, listeners) before the old module is replaced.import.meta.hot.data: persist state between versions.import.meta.hot.invalidate(): give up and propagate to importers.
Always guard with if (import.meta.hot); the code is removed from production builds.
Gotchas: React Fast Refresh only works when a file exports only components (mixed exports cause full reloads); modules with side effects such as global listeners need dispose; HMR over Docker or WSL may need server.watch.usePolling or server.hmr settings.
Example
// src/clock.js: a plain module that hot-swaps itself
let timer = setInterval(() => {
document.title = new Date().toLocaleTimeString();
}, 1000);
export function format(d) {
return d.toISOString();
}
if (import.meta.hot) {
// clean up side effects of this version before the new one runs
import.meta.hot.dispose(() => clearInterval(timer));
// accept updates: no full page reload when this file changes
import.meta.hot.accept((newModule) => {
console.log("clock updated", typeof newModule?.format);
});
}Remember
- HMR walks up importers to the nearest accepting boundary
- No boundary found means a full page reload
accept,dispose,data,invalidateon import.meta.hot- Guard with
if (import.meta.hot); stripped in production
Watch out for
- Exporting constants and components from the same React file, which breaks Fast Refresh.