Гайды

Почему не работает 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. Если на это сборщик среагировал, а на сохранение из редактора — нет, это оно.

Как починить.

  1. Лучший вариант — перенести проект внутрь WSL, например в ~/projects, и открыть его оттуда. VS Code умеет это через расширение WSL. Заодно всё станет быстрее.
  2. Если перенести нельзя — включи опрос файлов. В 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.

Как починить.

  1. Вынеси константы и утилиты в отдельный файл, например config.js. В файле компонента оставь только компоненты.
  2. Дай компоненту имя: export default function Header() { ... }.
  3. Разорви цикл: общий код вынеси в третий файл, который импортируют оба.

Почему hot reload работает только для CSS?

Скорее всего, это причина 3. Стили сборщик всегда подменяет на лету, а для JavaScript ему нужна граница, где можно остановиться. Если её нет, он откатывается к полной перезагрузке.

Можно ли нарочно сбрасывать состояние при каждом сохранении?

Да. Добавь в файл комментарий // @refresh reset — и компоненты из него будут монтироваться заново после каждой правки. Удобно, когда настраиваешь анимацию появления.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →