Setting Up a Vite Dev Server with Reliable Hot Module Replacement
Get a Vite dev server running, prove HMR actually works via the WebSocket check, and fix the common Docker/reverse-proxy cases where hot reload silently fails.
29 Jan 2026, 07:04 UTC

The outcome you want
You save a source file and the change appears in the browser in under a second, without a full page reload and without losing component state. That is Vite's Hot Module Replacement (HMR) working correctly. When it silently falls back to full reloads — or stops updating entirely — iteration speed collapses. This guide gets a Vite dev server running, verifies HMR actually works, and covers the common cases where it breaks (Docker, reverse proxies, non-standard ports).
Why Vite's HMR is fast (and when it isn't)
Vite serves source files as native ES modules during development instead of bundling everything up front. The browser requests modules directly, and Vite only transforms the file you actually import. When you edit a file, Vite pushes an update over a WebSocket connection to the browser, which swaps just that module. Framework plugins (React, Vue, Svelte) add state preservation, so a form you half-filled stays filled.
HMR reliability depends on two things: the WebSocket channel between the browser and the dev server, and your framework's HMR integration. Complex state held outside the component tree (module-level singletons, some global stores) may still need a manual refresh — that is a framework limitation, not a misconfiguration.
Prerequisites
- Node.js 18 or later (Vite 5.x assumption; check
node --version) - A terminal with permission to install npm packages
- Port 5173 free locally, or willingness to pick another
Create and start the project
Run these in your terminal as your normal user (no sudo needed):
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run devReplace react with vue, svelte, vanilla, or another listed template. The dev server starts on http://localhost:5173 by default.
Verify HMR actually works
Do not assume it works — check it:
- Open the printed URL in your browser and open DevTools (F12) → Console and Network tabs.
- In Network, filter by "WS" (WebSocket). You should see a connection to the dev server in a pending/open state. If there is no WebSocket, HMR cannot push updates.
- Edit a visible string in
src/App.jsx(or the equivalent) and save. The page should update without the browser tab flashing a reload, and the console should show an HMR update message rather than a full reload log. - Type something into a component input first, then edit the file. If the input keeps its value after the update, state-preserving HMR is working.
Fixing HMR behind Docker or a reverse proxy
The most common HMR failure: the page loads fine, but edits never appear. This almost always means the HMR WebSocket can't reach the dev server, typically because the browser is told to connect to the wrong host or port. Fix it explicitly in vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: true, // listen on all interfaces (required inside Docker)
port: 5173,
hmr: {
// host/port the BROWSER should use to reach the dev server
host: 'localhost',
clientPort: 5173,
},
},
})Key points:
host: truemakes Vite listen on0.0.0.0, required when the server runs in a container but the browser runs on your machine.hmr.clientPortis the port the browser dials. If you publish the container as-p 8080:5173, setclientPort: 8080.- Behind an HTTPS reverse proxy (nginx, Traefik), the WebSocket must be upgraded at the proxy and you may need
hmr.protocol: 'wss'. Verify the proxy forwards theUpgradeandConnectionheaders.
After changing vite.config.js, restart the dev server — config changes are not hot-reloaded. Then repeat the WebSocket check in DevTools.
Expected checks and limitations
- Check: WS connection open in DevTools Network tab; console logs an HMR update on save; component state survives edits.
- Limitation: native-ESM development assumes a modern browser. Very old browsers are a production-build concern, not a dev-server one.
- Limitation: files imported with side effects that run at module scope can behave differently after HMR; a hard refresh (Ctrl+Shift+R) is the quick diagnostic.
- Recovery: if updates stop entirely, restart the dev server and hard-refresh the browser. If it persists, delete
node_modules/.vite(the dependency pre-bundle cache) and restart — stale pre-bundled dependencies are a common culprit after changingpackage.json.
This changes only local project state; recovery is simply stopping the server and deleting generated caches. Nothing here touches production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.