Todos los artículos
ViteHMRlocal developmentlocalhost tunneling

Vite + túnel: HMR que de verdad funciona

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.local como VITE_PUBLIC_URL cuando el frontend necesita conocer su propia dirección pública. import.meta.env.VITE_PUBLIC_URL la lee en el cliente.

Paso a paso

  1. Añade el bloque server.host, allowedHosts y hmr a tu config de Vite.
  2. Reinicia el servidor de desarrollo.
  3. Ejecuta npx portpreview 5173 (o el puerto que use Vite).
  4. Abre la URL HTTPS en tu navegador o en un móvil.
  5. 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.

Preguntas frecuentes

¿Por qué Vite bloquea mi URL de túnel con "host is not allowed"?
Vite 5+ añadió una comprobación de host que rechaza hostnames desconocidos por defecto. Añade allowedHosts: ['.portpreview.dev'] (o tu dominio de túnel) a la config de server para aceptar peticiones del túnel.
¿Cómo hago que el HMR de Vite funcione a través de un túnel?
Pon hmr.clientPort: 443 y hmr.protocol: 'wss' en vite.config.ts. Esto le dice al cliente HMR que se conecte por el HTTPS/WSS del túnel en lugar de intentar alcanzar localhost directamente, lo cual falla desde fuera de tu máquina.
¿Funciona esto para SvelteKit, Astro y Nuxt?
Sí. Cualquier framework que use Vite como servidor de desarrollo toma la misma config de server.host, allowedHosts y hmr. Astro la anida bajo vite en astro.config.mjs; los demás la ponen en su archivo de config de Vite habitual.