Почему не работает hot reload — страница не обновляется после сохранения

Ты сохраняешь файл. Ждёшь. Страница не меняется. Жмёшь F5 — изменения на месте. Значит, код верный, а сломалось то, что должно было обновить страницу само.
Это hot reload, или HMR (Hot Module Replacement — горячая замена модулей). Сборщик следит за файлами и на лету подменяет изменённый кусок в браузере. Без перезагрузки и без потери введённых данных.
Ломается он по трём причинам. Но сначала — проверка на 10 секунд, которая сразу скажет, какая твоя.
Сначала: посмотри в терминал при сохранении
Сохрани файл и посмотри в окно, где запущен npm run dev:
- Ничего не появилось → сборщик не видит изменения. Это причина 1.
- Появилось
hmr updateилиpage reload, но страница прежняя → изменения видны, но не доходят. Это причина 2. - Страница обновилась, но сбросились форма и состояние → горячая замена превратилась в полную перезагрузку. Это причина 3.
И ещё одна мелочь, которая съедает полчаса. Если порт был занят, Vite пишет Port 5173 is in use, trying another one... и запускается на 5174. А у тебя в браузере открыта старая вкладка на 5173 — со старым сервером. Сверь адрес с тем, что напечатал терминал. Про остальные проблемы с адресом — в статье почему не работает localhost.
Причина 1. Сборщик не видит, что файл изменился
Самая частая причина у тех, кто на Windows. Сборщик узнаёт об изменениях от операционной системы: «вот этот файл только что сохранили». Но такие сигналы не всегда доходят.
Где это ломается:
- WSL2. Проект лежит на диске Windows (
/mnt/c/...), а редактор запущен в Windows. Сигналы об изменениях через эту границу не проходят — так прямо написано в документации Vite. - Docker на Windows и Mac. Папка проекта подключена в контейнер, а сохраняешь ты снаружи.
- Общие папки виртуальных машин, например в VirtualBox.
Как проверить. Создай новый файл в проекте прямо из терминала WSL или контейнера: touch src/test.js. Если на это сборщик среагировал, а на сохранение из редактора — нет, это оно.
Как починить.
- Лучший вариант — перенести проект внутрь WSL, например в
~/projects, и открыть его оттуда. VS Code умеет это через расширение WSL. Заодно всё станет быстрее. - Если перенести нельзя — включи опрос файлов. В
vite.config.js:
export default {
server: {
watch: { usePolling: true },
},
};
В Next.js то же самое делает переменная окружения: WATCHPACK_POLLING=true npm run dev. Учти: опрос нагружает процессор, сборщик проверяет файлы сам по таймеру.
Если ты на Linux и видишь ошибку ENOSPC: System limit for number of file watchers reached — система упёрлась в лимит наблюдаемых файлов. Подними его:
sudo sysctl fs.inotify.max_user_watches=524288
Причина 2. В импорте другой регистр букв
Файл называется header.jsx, а импортируешь ты ./Header. На Mac и Windows это работает: их файловые системы не различают большие и маленькие буквы. Страница открывается.
Но сборщик теперь считает это двумя разными файлами. Ты меняешь один, а страница держится за другой. Обновление уходит в никуда.
Как проверить. Найди импорт изменённого файла и сверь каждую букву с настоящим именем.
Как починить. Исправь импорт так, чтобы он совпадал с именем файла до буквы. Это заодно спасёт тебя от падения деплоя: на сервере Linux регистр важен, и там такой импорт просто не найдётся.
Причина 3. Обновляется, но сбрасывается состояние
В React горячую замену делает механизм Fast Refresh. Он старается сохранить состояние компонента: текст в полях, открытые вкладки. Но в нескольких случаях сдаётся и перезагружает страницу целиком.
Частые случаи:
- Файл экспортирует не только компоненты. Например, рядом с компонентом лежит
export const API_URL = ..., и эту константу импортирует обычный файл, не компонент. Тогда Fast Refresh перезагружает всё. В Vite в терминале будет похожее наCould not Fast Refresh. - Безымянный компонент.
export default () => <div />— у стрелочной функции нет имени, и состояние при каждом сохранении сбрасывается. - Классовый компонент вместо функции.
- Циклический импорт: файл A импортирует B, а B — A. Vite в этом случае перезагружает страницу. Найти цикл поможет
vite --debug hmr.
Как починить.
- Вынеси константы и утилиты в отдельный файл, например
config.js. В файле компонента оставь только компоненты. - Дай компоненту имя:
export default function Header() { ... }. - Разорви цикл: общий код вынеси в третий файл, который импортируют оба.
Почему hot reload работает только для CSS?
Скорее всего, это причина 3. Стили сборщик всегда подменяет на лету, а для JavaScript ему нужна граница, где можно остановиться. Если её нет, он откатывается к полной перезагрузке.
Можно ли нарочно сбрасывать состояние при каждом сохранении?
Да. Добавь в файл комментарий // @refresh reset — и компоненты из него будут монтироваться заново после каждой правки. Удобно, когда настраиваешь анимацию появления.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





