Туннелировать 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читает его в клиенте.
Пошагово
- Добавьте блок
server.host,allowedHostsиhmrв конфиг Vite. - Перезапустите dev-сервер.
- Выполните
npx portpreview 5173(или порт, который использует Vite). - Откройте HTTPS-URL в браузере или на телефоне.
- Отредактируйте компонент. Убедитесь, что HMR обновляет страницу без полной перезагрузки.
Если HMR не обновляет, откройте консоль браузера — Vite логирует попытку WebSocket-соединения, и вы увидите URL, к которому он обращался. Это покажет, сработало ли изменение hmr.clientPort/protocol.
Присоединяйтесь к листу ожидания PortPreview ради туннелей, которые по умолчанию сохраняют WebSocket-upgrade.