Все статьи
ViteHMRlocal developmentlocalhost tunneling

Vite + туннель: HMR, который реально работает

Туннелировать dev-сервер Vite в целом просто — кроме двух вещей, на которых вы споткнётесь в первый раз. Страница грузится, а HMR — нет. Или страница вообще не грузится, потому что Vite отвергает хостнейм туннеля. Оба случая решаются одной строкой конфига, когда знаешь, куда смотреть.

Проблема 1: «Blocked request. This host is not allowed.»

В Vite 5+ по умолчанию добавлена защита host-check. Если вы открываете URL туннеля и видите Blocked request. This host is not allowed, это проверка отвергает ваш хостнейм *.portpreview.dev.

Исправление в vite.config.ts:

export default defineConfig({
  server: {
    host: true,                            // listen on 0.0.0.0
    allowedHosts: ['.portpreview.dev'],   // accept any subdomain
  },
});

Ведущая точка делает это совпадением по суффиксу. Можно указать там конкретный хостнейм туннеля, если хотите строгости, но для повседневной разработки суффикс удобен — URL сессий туннеля меняются.

Проблема 2: HMR загружается, затем тихо умирает

HMR работает по WebSocket. Когда Vite раздаёт на localhost:5173, а браузер грузит с https://abc123.portpreview.dev, HMR-клиент пытается подключиться по URL, который ему назвал сервер, — локальному. Туннель его не пробрасывает, поэтому WebSocket падает и HMR тихо останавливается.

Исправьте конфиг HMR:

export default defineConfig({
  server: {
    host: true,
    allowedHosts: ['.portpreview.dev'],
    hmr: {
      clientPort: 443,            // browser connects on 443 (HTTPS)
      protocol: 'wss',            // secure WebSocket through the tunnel
    },
  },
});

Теперь HMR-клиент подключается к wss://abc123.portpreview.dev (порт 443), туннель пробрасывает upgrade, и правки распространяются как ожидается. Если вам нужна только статическая перезагрузка и HMR через туннель не важен, это можно пропустить — но придётся много жать cmd-R.

Зачем вообще туннелировать Vite

Три реальные причины:

  • Мобильное тестирование. Откройте URL на телефоне, увидите тот же билд с HMR. Лучше, чем шеринг экрана или эмуляторы под каждое устройство. Полный паттерн см. в мобильном тестировании через туннель.
  • Делиться работой в процессе. Отправьте ссылку дизайнеру или PM — они увидят вашу ветку вживую. Без шага деплоя.
  • Флоу OAuth и вебхуков, требующие HTTPS. Раздаваемые Vite фронтенды, общающиеся со Stripe (только redirect), Auth0 и подобными. Выбор разобран в mkcert vs туннель.

SvelteKit, Astro, SolidStart — все на Vite

Конфиги выше применимы к любому фреймворку, который раздаёт Vite как dev-сервер. У SvelteKit vite.config.js той же формы. У Astro собственный astro.config.mjs с подобъектом vite — поместите те же опции server туда. SolidStart, Nuxt 3 (через Vite) и Qwik City: тот же паттерн.

Production-билды — другая история

Всё выше — конфиг dev-сервера. Когда вы запускаете vite preview или раздаёте собранный бандл, ничего из этого не важно, потому что нет ни HMR, ни мидлвара host-check. На этом этапе туннель просто пробрасывает статические файлы.

Несколько мелочей

  • Строгий host-CORS. Если фронтенд обращается к API на другом origin (другой порт localhost или отдельный API-туннель), настройте CORS на стороне API. Vite по умолчанию не проксирует, пока вы не укажете это через server.proxy.
  • Приложения с активным WebSocket. HMR Vite — это один WebSocket. Если приложение использует другой WebSocket для состояния игры, чата или живых данных, он отдельный и тоже должен быть настроен в клиентском коде на URL туннеля.
  • URL туннеля в env-файлах. Мы кладём активный URL туннеля в .env.local как VITE_PUBLIC_URL, когда фронтенду нужно знать собственный публичный адрес. import.meta.env.VITE_PUBLIC_URL читает его в клиенте.

Пошагово

  1. Добавьте блок server.host, allowedHosts и hmr в конфиг Vite.
  2. Перезапустите dev-сервер.
  3. Выполните npx portpreview 5173 (или порт, который использует Vite).
  4. Откройте HTTPS-URL в браузере или на телефоне.
  5. Отредактируйте компонент. Убедитесь, что HMR обновляет страницу без полной перезагрузки.

Если HMR не обновляет, откройте консоль браузера — Vite логирует попытку WebSocket-соединения, и вы увидите URL, к которому он обращался. Это покажет, сработало ли изменение hmr.clientPort/protocol.

Присоединяйтесь к листу ожидания PortPreview ради туннелей, которые по умолчанию сохраняют WebSocket-upgrade.

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

Почему Vite блокирует URL туннеля с «host is not allowed»?
В Vite 5+ добавлена проверка host, по умолчанию отвергающая неизвестные хостнеймы. Добавьте allowedHosts: ['.portpreview.dev'] (или ваш домен туннеля) в конфиг server, чтобы принимать запросы туннеля.
Как заставить HMR Vite работать через туннель?
Задайте hmr.clientPort: 443 и hmr.protocol: 'wss' в vite.config.ts. Это говорит HMR-клиенту подключаться через HTTPS/WSS туннеля, а не пытаться достучаться до localhost напрямую, что не работает извне вашей машины.
Работает ли это для SvelteKit, Astro и Nuxt?
Да. Любой фреймворк, использующий Vite как dev-сервер, принимает тот же конфиг server.host, allowedHosts и hmr. Astro вкладывает его под vite в astro.config.mjs; остальные кладут в обычный файл конфига Vite.