Implementing Vite HMR: Preserve State During Development Without Full Reloads
Preserve component state during development by implementing Vite's Hot Module Replacement correctly. Learn to verify HMR works and fix common issues like circular dependencies and file watcher limits.
16 Dec 2025, 15:18 UTC

The Problem: Losing State on Every Save
When developing frontend applications, constantly losing component state—form inputs, UI selections, scroll positions—on every file save slows iteration. Full page reloads break the development flow, especially when debugging complex state interactions.
Vite's HMR Solution
Vite solves this by leveraging native ES modules (ESM) in the browser. Instead of bundling everything and reloading, it invalidates only the modified module and hot-replaces it in place. This triggers updates without discarding the current page state.
Prerequisites
- Node.js 18+ installed
- A Vite project with Vue or React (or vanilla JS)
- Basic understanding of ES modules
Step-by-Step HMR Setup
- Start the Vite dev server
Run this in your project root. The server starts atnpx vitehttp://localhost:5173by default. - Modify a module and observe
Edit the template or script, save the file. The button updates instantly; the count value persists.// src/components/Counter.vue (Vue example) <script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">{{ count }}</button> </template> - Enable custom HMR logic
This pattern lets you run cleanup or reinitialize logic when modules update.// src/stores/counter.js import { ref } from 'vue' export const count = ref(0) if (import.meta.hot) { import.meta.hot.accept(() => { console.log('Counter store updated without reload') }) }
Expected Checks
- Browser console shows
[HMR] UpdatedorHMR updatedmessages after saving - Component state (e.g., counter value) persists across edits
- No full page refresh in Network tab (status 200 for HMR websocket, not document reload)
Framework Integration
Vite includes built-in plugins for Vue and React that handle component-level HMR automatically. For Vue:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [vue()] })
For React, create the config with:
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({ plugins: [react()] })
Common Issues and Fixes
Circular Dependencies Break HMR
If modules A and B import each other, Vite falls back to full reload. Check console for warnings like [HMR] cannot accept change due to circular dependency. Refactor to use dependency injection or dynamic imports.
File Watcher Limits on Linux
Large projects may hit inotify limits. Increase with:
# Run as root or via sudo
echo 524288 | tee -a /proc/sys/fs/inotify/max_user_watches
CSS-Only Changes
CSS updates work out of the box. Add a style block to test:
<style scoped>
.button { background: blue; }
</style>
Save and watch the color change without touching JS.
Verification Checklist
- Start
npx viteand open the app - Edit any component's template or script
- Confirm browser updates without refresh (check Network tab)
- Verify state persists (e.g., click a button, edit code, see updated UI with same count)
- Check console for HMR messages
Limitations
- Custom
import.meta.hotlogic only runs in development; build tools strip it - State preservation depends on framework plugin correctness
- Very large numbers of small files can strain OS file watchers
Recovery Options
If HMR stops working, force a full reload with:
# In browser console
window.location.reload(true)
Or restart the Vite server. Check for errors in the terminal output indicating module parsing failures or websocket disconnection.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.