Tunelizar un servidor de desarrollo de Vite funciona bien, salvo por dos cosas que te harán tropezar la primera vez. La página carga pero el HMR no. O la página no carga porque Vite rechaza el hostname de tu túnel. Ambas son una línea de config una vez sabes dónde mirar.
Problema 1: "Blocked request. This host is not allowed."
Vite 5+ añadió por defecto una protección de comprobación de host. Si abres la URL de tu túnel y ves Blocked request. This host is not allowed, es esa comprobación rechazando tu hostname *.portpreview.dev.
Solución en vite.config.ts:
export default defineConfig({
server: {
host: true, // listen on 0.0.0.0
allowedHosts: ['.portpreview.dev'], // accept any subdomain
},
});
El punto inicial lo convierte en una coincidencia por sufijo. Puedes poner ahí tu hostname de túnel específico si prefieres ser estricto, pero para el día a día el sufijo es cómodo: las URLs de sesión del túnel rotan.
Problema 2: el HMR carga y luego muere en silencio
El HMR funciona sobre WebSocket. Cuando Vite sirve en localhost:5173 pero tu navegador carga desde https://abc123.portpreview.dev, el cliente HMR intenta conectarse a la URL que el servidor le indicó, que es la local. El túnel no la reenvía, así que el WebSocket falla y el HMR se detiene en silencio.
Corrige la config de 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
},
},
});
Ahora el cliente HMR se conecta a wss://abc123.portpreview.dev (puerto 443), el túnel reenvía el upgrade y las ediciones se propagan como se espera. Si solo necesitas recarga estática y no te importa el HMR por el túnel, puedes saltarte esto, pero usarás mucho cmd-R.
Por qué querrías tunelizar Vite
Tres razones reales:
- Pruebas móviles. Abre la URL en un móvil, ve el mismo build con HMR. Mejor que compartir pantalla o emuladores por dispositivo. Mira pruebas móviles con un túnel para el patrón completo.
- Compartir trabajo en curso. Envía un enlace a un diseñador o PM y ven tu rama en vivo. Sin paso de despliegue.
- Flujos de OAuth y webhooks que necesitan HTTPS. Frontends servidos por Vite que hablan con Stripe (solo redirect), Auth0 o similares. mkcert vs. túnel cubre la elección.
SvelteKit, Astro, SolidStart usan Vite
Las configs anteriores aplican a cualquier framework que use Vite como servidor de desarrollo. El vite.config.js de SvelteKit tiene la misma forma. Astro tiene su propio astro.config.mjs con un subobjeto vite: pon ahí las mismas opciones de server. SolidStart, Nuxt 3 (vía Vite) y Qwik City: el mismo patrón.
Los builds de producción son otra historia
Todo lo anterior es config del servidor de desarrollo. Cuando ejecutas vite preview o sirves un bundle compilado, nada de esto importa porque no hay HMR ni middleware de comprobación de host. El túnel solo reenvía archivos estáticos en ese punto.
Algunas cosas menores
- CORS de host estricto. Si tu frontend llama a una API en otro origen (otro puerto de localhost o un túnel de API separado), configura CORS en el lado de la API. Vite no hace proxy por defecto a menos que se lo indiques con
server.proxy. - Apps con mucho WebSocket. El HMR de Vite es un WebSocket. Si tu app usa otro WebSocket para estado de juego, chat o datos en vivo, ese es aparte y también debe configurarse en tu código cliente para usar la URL del túnel.
- URL del túnel en archivos de entorno. Ponemos la URL del túnel activo en
.env.localcomoVITE_PUBLIC_URLcuando el frontend necesita conocer su propia dirección pública.import.meta.env.VITE_PUBLIC_URLla lee en el cliente.
Paso a paso
- Añade el bloque
server.host,allowedHostsyhmra tu config de Vite. - Reinicia el servidor de desarrollo.
- Ejecuta
npx portpreview 5173(o el puerto que use Vite). - Abre la URL HTTPS en tu navegador o en un móvil.
- Edita un componente. Confirma que el HMR actualiza la página sin recarga completa.
Si el HMR no actualiza, abre la consola del navegador: Vite registra el intento de conexión WebSocket y verás la URL que intentó. Eso te dice si el cambio de hmr.clientPort/protocol surtió efecto.
Únete a la lista de espera de PortPreview para túneles que preservan los upgrades de WebSocket por defecto.