Why hot reload doesn't work — the page won't update after you save

You save the file. You wait. The page doesn't change. You hit F5 — and the changes are there. So the code is fine; what broke is the thing that was supposed to update the page on its own.
That's hot reload, or HMR (Hot Module Replacement). The bundler watches your files and swaps the changed piece in the browser on the fly. No reload, and no losing what you'd typed.
It breaks for three reasons. But first — a 10-second check that tells you which one is yours.
First: watch the terminal when you save
Save a file and look at the window running npm run dev:
- Nothing appeared → the bundler can't see the change. That's cause 1.
hmr updateorpage reloadappeared, but the page is the same → the change is seen but doesn't arrive. That's cause 2.- The page updated, but your form and state reset → hot replacement turned into a full reload. That's cause 3.
One more little thing that eats half an hour. If the port was busy, Vite prints Port 5173 is in use, trying another one... and starts on 5174. Meanwhile your browser has an old tab open on 5173 — with an old server. Match the address against what the terminal printed. Other address problems are covered in why localhost isn't working.
Cause 1. The bundler doesn't see that the file changed
The most common cause for people on Windows. The bundler learns about changes from the operating system: "this file was just saved". But those signals don't always get through.
Where it breaks:
- WSL2. The project sits on the Windows drive (
/mnt/c/...) and the editor runs in Windows. Change signals don't cross that boundary — the Vite docs say so directly. - Docker on Windows and Mac. The project folder is mounted into a container, but you save from outside.
- Virtual machine shared folders, e.g. in VirtualBox.
How to check. Create a new file in the project right from the WSL or container terminal: touch src/test.js. If the bundler reacted to that but not to saving from your editor, this is it.
How to fix.
- Best option — move the project inside WSL, e.g. to
~/projects, and open it from there. VS Code does this via the WSL extension. Everything gets faster too. - If you can't move it — turn on file polling. In
vite.config.js:
export default {
server: {
watch: { usePolling: true },
},
};
In Next.js an environment variable does the same: WATCHPACK_POLLING=true npm run dev. Keep in mind polling loads the CPU — the bundler checks files itself on a timer.
If you're on Linux and see ENOSPC: System limit for number of file watchers reached — the system hit its limit on watched files. Raise it:
sudo sysctl fs.inotify.max_user_watches=524288
Cause 2. The import uses different letter case
The file is called header.jsx, but you import ./Header. On Mac and Windows this works: their file systems don't distinguish upper and lower case. The page opens.
But the bundler now treats them as two different files. You change one while the page holds on to the other. The update goes nowhere.
How to check. Find the import of the file you changed and compare every letter with the real file name.
How to fix. Make the import match the file name exactly. It'll also save your deploy: on a Linux server case matters, and that import simply won't be found.
Cause 3. It updates, but the state resets
In React, hot replacement is done by a mechanism called Fast Refresh. It tries to keep your component's state: text in fields, open tabs. But in a few cases it gives up and reloads the whole page.
Common cases:
- The file exports more than components. Say a component file also has
export const API_URL = ..., and a regular non-component file imports that constant. Then Fast Refresh reloads everything. In Vite the terminal will show something likeCould not Fast Refresh. - An unnamed component.
export default () => <div />— the arrow function has no name, so state resets on every save. - A class component instead of a function.
- A circular import: file A imports B and B imports A. In that case Vite reloads the page.
vite --debug hmrhelps you find the cycle.
How to fix.
- Move constants and utilities into a separate file, e.g.
config.js. Keep only components in the component file. - Give the component a name:
export default function Header() { ... }. - Break the cycle: move the shared code into a third file that both import.
Why does hot reload only work for CSS?
Most likely it's cause 3. The bundler always swaps styles on the fly, but for JavaScript it needs a boundary where it can stop. Without one, it falls back to a full reload.
Can I deliberately reset state on every save?
Yes. Add the comment // @refresh reset to the file — and its components will remount after every edit. Handy when you're tuning an entrance animation.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





