wandres.dev
CONFIGURAR VITE · plugins y resolve

server: proxy, host y las opciones de desarrollo

La clave server de Vite: proxy para hablar con APIs sin CORS, host y port para exponer el dev server, cors y allowedHosts para no dejar la puerta abierta, y las opciones de desarrollo que más se tocan en 2026.

⏱ 17 min

El servidor de desarrollo de Vite es una pieza distinta del build: no empaqueta, sirve módulos bajo demanda y recarga en caliente. La clave server gobierna ese servidor, y su opción estrella —proxy— resuelve el problema que todo frontend encuentra el primer día: cómo hablar con una API en otro puerto sin chocar contra CORS. Junto a ella, host y port deciden quién puede alcanzar tu dev server, y un grupo de opciones de seguridad —cors, allowedHosts— existe porque un servidor de desarrollo abierto a la red es una superficie de ataque real. Esta lección cubre el server que de verdad se configura.

🎯 Al terminar esta lección sabrás
  • Configurar proxy para hablar con APIs sin tropezar con CORS.
  • Exponer el dev server con host, port y strictPort.
  • Entender cors y allowedHosts como límites de seguridad.
  • Afinar la experiencia de desarrollo con open, warmup y hmr.

proxy: el navegador cree que todo es el mismo origen

El navegador aplica la política del mismo origen: una página servida en un puerto no puede llamar por defecto a una API en otro sin que esta devuelva las cabeceras CORS correctas. En desarrollo eso es una fricción constante, porque tu front vive en un puerto de Vite y tu backend en otro. El proxy la elimina: le dices a Vite que ciertas rutas se reenvíen a otro servidor, y como la petición del navegador va siempre al origen de Vite, no hay cruce de orígenes que bloquear.

import { defineConfig } from "vite"

export default defineConfig({
  server: {
    proxy: {
      // string corto: /api se reenvia a http://localhost:8080/api
      "/api": "http://localhost:8080",
      // objeto: control total
      "/v2": {
        target: "https://backend.internal",
        changeOrigin: true,
        secure: false,
        rewrite: (path) => path.replace(/^\/v2/, ""),
      },
      // websockets
      "/socket": {
        target: "ws://localhost:5174",
        ws: true,
      },
      // clave como expresion regular
      "^/legacy/.*": {
        target: "http://localhost:9000",
        changeOrigin: true,
      },
    },
  },
})

Las tres opciones que marcan la diferencia son changeOrigin, secure y rewrite. changeOrigin reescribe la cabecera Host para que el backend crea que la petición vino de él mismo —imprescindible con hosts virtuales o APIs en la nube—. secure en false acepta certificados HTTPS autofirmados, típico de backends de desarrollo. Y rewrite transforma la ruta antes de reenviarla, para quitar un prefijo que solo existe en tu lado y no en el del servidor real.

flowchart LR
A[navegador] --> B[vite dev server 5173]
B --> C[la ruta casa con una regla de proxy]
C --> D[reenvia al backend 8080]
D --> E[respuesta vuelve por vite]
E --> A
style C fill:#89b4fa,color:#11111b

host, port y el acceso desde la red

Por defecto el dev server solo escucha en localhost, alcanzable únicamente desde tu máquina. Poner host en true lo hace escuchar en todas las interfaces, y entonces un móvil o un compañero en la misma red pueden abrirlo por tu IP local: la forma canónica de probar en un dispositivo real. port fija el puerto y strictPort decide qué pasa si está ocupado: por defecto Vite salta al siguiente libre, pero en un pipeline donde otra herramienta espera un puerto exacto conviene que falle en vez de moverse.

server: {
  host: true,        // escucha en todas las interfaces, accesible desde la LAN
  port: 5173,
  strictPort: true,  // falla si el puerto esta ocupado en vez de saltar
  open: "/panel",    // abre el navegador directamente en esa ruta
  warmup: {
    clientFiles: ["./src/main.ts", "./src/App.vue"],
  },
}

open arranca el navegador por ti, y si le das una ruta abre directamente en ella. warmup es una optimización más fina y moderna: le adelantas a Vite qué archivos vas a pedir primero para que los transforme antes de que los solicites, recortando el tiempo hasta la primera pantalla en proyectos grandes. Es gratis y casi nadie lo usa.

Un detalle que ahorra depuraciones: cuando sirves la app bajo un subdirectorio, la clave base y el server.origin deben concordar con esa ruta pública, o los assets se pedirán a una URL que no existe. Es el mismo mapa de rutas que impone el proxy, visto ahora desde el lado de los recursos estáticos en lugar del de las llamadas a la API.

🔀

proxy

Reenvía rutas a otro servidor para disolver CORS en desarrollo. Recuerda replicarlo en el reverse proxy de producción.

📡

host

En true escucha en toda la red y expone el dev server a otros dispositivos. Ábrelo solo cuando de verdad lo necesites.

🔒

allowedHosts

La lista de nombres con los que se puede alcanzar el servidor. Tu defensa contra el DNS rebinding.

🔥

warmup

Precalienta los archivos de entrada para recortar el tiempo hasta la primera pantalla. Gratis y casi siempre olvidado.

cors, allowedHosts y seguridad en dev

Un dev server no es un juguete inofensivo: sirve tu código fuente y, mal expuesto, deja que un sitio malicioso o un host suplantado lea cosas que no debería. Por eso las versiones recientes de Vite endurecieron los defaults, y conviene entender las dos palancas. cors controla qué orígenes externos pueden pedir recursos a tu dev server; dejarlo abierto de par en par ya no es el default. allowedHosts es la defensa contra el DNS rebinding: la lista de nombres de host con los que se puede llegar a tu servidor, para que un dominio atacante que resuelva a tu IP no pueda hablar con él.

server: {
  cors: false,
  allowedHosts: ["app.local", ".mi-tunel.dev"],
  hmr: {
    overlay: true,
  },
}
⚠️
No abras el dev server más de lo que necesitas

Es tentador poner host en true, cors abierto y allowedHosts en permitir todo para que algo funcione ya. Cada una de esas decisiones amplía la superficie de ataque de una máquina que tiene tu código y, a menudo, tus tokens de desarrollo en memoria. Abre solo lo justo: host cuando de verdad pruebes en otro dispositivo, un origen concreto en cors, y el nombre exacto de tu túnel en allowedHosts. La comodidad de hoy no vale un incidente de seguridad mañana.

ℹ️
El servidor de preview no es el de desarrollo

server configura el dev server, que sirve módulos sin transformar y con recarga en caliente. Cuando ejecutas la vista previa de un build ya empaquetado, manda la clave preview, que tiene sus propias opciones de port, host y proxy. No las confundas: probar producción en local usa preview, y su config es independiente de la de desarrollo.

https, hmr y watch: afinar el bucle

Cerrado el proxy y el acceso, quedan tres opciones que afinan el bucle de desarrollo minuto a minuto: el cifrado, la reconexión en caliente y el vigilante de archivos. Ninguna es glamurosa, y las tres deciden si trabajar en el proyecto se siente instantáneo o pastoso.

A veces necesitas HTTPS en desarrollo: para probar APIs que exigen origen seguro, service workers, o cookies con atributos estrictos que el navegador solo respeta sobre TLS. La opción https acepta las rutas del certificado y la clave, y en la práctica se combina con un plugin que genera certificados de confianza local para no pelearte con los avisos del navegador.

server: {
  https: { key: "./certs/key.pem", cert: "./certs/cert.pem" },
  hmr: { host: "localhost", protocol: "ws", overlay: true },
  watch: {
    ignored: ["**/coverage/**", "**/.git/**"],
  },
}

El reemplazo de módulos en caliente casi nunca hay que tocarlo, pero cuando el dev server vive tras un proxy o en un puerto distinto del que ve el usuario, hmr deja fijar el host, el puerto o el protocolo del websocket para que el cliente sepa a dónde reconectarse. El overlay decide si los errores se pintan sobre la página o solo van a la consola.

watch ajusta el vigilante de archivos. ignored excluye carpetas que no aportan —artefactos de cobertura, salidas de otras herramientas— y que, vigiladas, solo gastan recursos y disparan recargas espurias. En proyectos grandes o sobre sistemas de archivos de red, afinar el watcher es la diferencia entre un dev server ágil y uno que consume un núcleo entero sin motivo.

📝
Cuando el watcher no ve tus cambios

Dentro de un contenedor, de WSL o de un volumen montado, los eventos nativos del sistema de archivos a menudo no se propagan al proceso de Vite, y guardar un archivo no dispara la recarga. El escape es activar el sondeo en server.watch: en vez de esperar eventos, Vite pregunta por cambios en intervalos. Funciona siempre, a cambio de más CPU, así que resérvalo para los entornos donde los eventos nativos fallan, no para tu máquina local.

El proxy es una ilusión de dev que producción tiene que hacer real

El proxy de Vite hace algo casi mágico: disuelve CORS haciendo que, desde el punto de vista del navegador, backend y frontend compartan origen. Pero esa palabra —ilusión— es la clave que hay que interiorizar. En desarrollo, Vite se pone en medio y reenvía; en producción, Vite no existe, y esa misma unidad de origen tiene que fabricarla otra pieza: un reverse proxy real, un Nginx, un Caddy, un router de tu plataforma edge, que enrute /api al backend y el resto al build estático. Quien no entiende que el proxy es solo un andamio de desarrollo despliega, funciona en local y se estrella en producción con errores de CORS que en dev nunca vio, porque el andamio se quedó en su máquina. La lección profunda es que el dev server y el servidor de producción resuelven el mismo problema —quién responde a cada ruta— con mecanismos distintos, y que tu trabajo es mantener las dos topologías coherentes: la del proxy de Vite y la del reverse proxy de despliegue deben describir el mismo mapa de rutas. Cuando ese mapa coincide, dev predice producción; cuando diverge, dev miente. Y un dev server que miente sobre cómo se comportará producción es peor que no tener dev server, porque te da confianza justo donde deberías tener dudas. Configura proxy, sí, pero documenta a su lado la regla de despliegue equivalente, y trata las dos como una sola verdad expresada en dos lugares.

⚔️ Monta y prueba un proxy real
  1. Levanta un backend en otro puerto y configura server.proxy para reenviar /api a él; confirma en la pestaña de red que no hay error de CORS.
  2. Añade changeOrigin y un rewrite que quite el prefijo /api, y verifica en el backend que la ruta llega limpia.
  3. Pon host en true y abre el dev server desde tu móvil en la misma red por la IP local.
  4. Ajusta allowedHosts para permitir solo un nombre concreto y comprueba que otro es rechazado.
  5. Escribe, junto a tu proxy, la regla de reverse proxy equivalente que necesitarías en producción para el mismo /api.