Как тестировать фронт и бэк с HMR без сборки

    При разработке веб-приложений часто возникает задача: запустить одновременно фронтенд и бэкенд с поддержкой горячей перезагрузки модулей (HMR - Hot Module Replacement). Стандартный подход - собрать фронт через npm run build и разместить статику в папке бэка, но это убивает HMR. Разберём, как настроить dev-среду, чтобы оба сервера работали параллельно и изменения применялись мгновенно.

    Проблема: билд фронта убивает HMR

    Когда вы запускаете npm run build, сборщик (например, Webpack или Vite) создаёт оптимизированные статические файлы. Они кладутся в папку бэка, после чего запускается node server.js. В этом режиме HMR не работает - любое изменение фронта требует повторного билда. Для разработки это неудобно.

    Решение: параллельный запуск dev-режимов

    Оптимальный способ - запустить фронт в dev-режиме (с HMR) и бэк в dev-режиме (например, с nodemon или ts-node-dev), а затем настроить прокси на бэкенде, чтобы он перенаправлял запросы к API и отдавал статику из dev-сервера фронта.

    Шаг 1. Запустите фронт в dev-режиме

    Для проектов на React/Vue/Angular обычно используется команда npm run dev. Она запускает dev-сервер (например, на порту 3000) с HMR. Убедитесь, что все изменения сохраняются и страница перезагружается автоматически.

    Шаг 2. Запустите бэк в dev-режиме

    Для Node.js-бэкенда используйте nodemon или ts-node-dev с флагом --watch. Команда может выглядеть так: nodemon server.js или ts-node-dev --respawn src/server.ts. Бэк должен слушать свой порт (например, 5000).

    Шаг 3. Настройте прокси на бэкенде

    В бэкенде (Express, Koa, Fastify) добавьте middleware для проксирования запросов к статике на dev-сервер фронта. Пример для Express:

    const express = require('express');
    const { createProxyMiddleware } = require('http-proxy-middleware');
    const app = express();
    
    // Прокси для статики фронта
    app.use('/', createProxyMiddleware({
      target: 'http://localhost:3000', // порт dev-сервера фронта
      changeOrigin: true
    }));
    
    // API-маршруты
    app.use('/api', apiRouter);
    
    app.listen(5000);

    Теперь при обращении к http://localhost:5000 бэк отдаёт статику с фронтового dev-сервера, а API-запросы обрабатывает сам.

    Шаг 4. Используйте инструменты для параллельного запуска

    Чтобы не запускать два терминала вручную, установите concurrently или npm-run-all. Пример скрипта в package.json:

    "scripts": {
      "dev": "concurrently \"npm run dev:front\" \"npm run dev:back\"",
      "dev:front": "cd frontend && npm run dev",
      "dev:back": "cd backend && nodemon server.js"
    }

    Теперь одной командой npm run dev запускаются оба сервера с HMR.

    Альтернативные подходы

    • Использование одного порта через reverse proxy - nginx или Caddy могут выступать единой точкой входа, перенаправляя запросы на разные порты.
    • Монорепозиторий с общей конфигурацией - инструменты вроде Nx или Turborepo позволяют управлять зависимостями и запускать dev-серверы для нескольких приложений.
    • Docker Compose - если вы используете контейнеризацию, можно поднять два контейнера и связать их через внутреннюю сеть.

    Почему это работает

    Dev-сервер фронта поддерживает HMR и отдаёт обновлённые модули через WebSocket. Бэкенд не трогает статику - он только проксирует запросы. Таким образом, изменения в коде фронта мгновенно отображаются в браузере, а бэк перезагружается при изменении своих файлов. Никаких билдов, только разработка.

    Часто задаваемые вопросы