Tunelar um servidor de desenvolvimento do Vite funciona bem — exceto por duas coisas que vão te derrubar na primeira vez. A página carrega mas o HMR não. Ou a página não carrega de jeito nenhum porque o Vite recusa o hostname do seu túnel. Ambas são uma linha de config quando você sabe onde olhar.
Problema 1: "Blocked request. This host is not allowed."
O Vite 5+ adicionou por padrão uma proteção de checagem de host. Se você abre a URL do seu túnel e vê Blocked request. This host is not allowed, é essa checagem recusando seu hostname *.portpreview.dev.
Correção no vite.config.ts:
export default defineConfig({
server: {
host: true, // listen on 0.0.0.0
allowedHosts: ['.portpreview.dev'], // accept any subdomain
},
});
O ponto inicial transforma isso em uma correspondência por sufixo. Você pode colocar ali seu hostname de túnel específico se preferir ser estrito, mas para o dia a dia o sufixo é conveniente — as URLs de sessão do túnel rotacionam.
Problema 2: o HMR carrega e depois morre em silêncio
O HMR roda sobre WebSocket. Quando o Vite serve em localhost:5173 mas seu navegador carrega de https://abc123.portpreview.dev, o cliente HMR tenta se conectar à URL que o servidor informou — a local. O túnel não a encaminha, então o WebSocket falha e o HMR para em silêncio.
Corrija a config do 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
},
},
});
Agora o cliente HMR se conecta a wss://abc123.portpreview.dev (porta 443), o túnel encaminha o upgrade e as edições se propagam como esperado. Se você só precisa de recarga estática e não liga para HMR pelo túnel, pode pular isto — mas vai usar muito cmd-R.
Por que você ia querer tunelar o Vite
Três razões reais:
- Testes em mobile. Abra a URL num celular, veja o mesmo build com HMR. Melhor que compartilhar tela ou emuladores por dispositivo. Veja testes em mobile com um túnel para o padrão completo.
- Compartilhar trabalho em andamento. Mande um link para um designer ou PM, eles veem sua branch ao vivo. Sem etapa de deploy.
- Fluxos de OAuth e webhooks que precisam de HTTPS. Frontends servidos pelo Vite que falam com Stripe (só redirect), Auth0 ou similares. mkcert vs. túnel cobre a escolha.
SvelteKit, Astro, SolidStart usam Vite
As configs acima valem para qualquer framework que entrega o Vite como servidor de desenvolvimento. O vite.config.js do SvelteKit tem o mesmo formato. O Astro tem o próprio astro.config.mjs com um subobjeto vite — coloque as mesmas opções de server ali. SolidStart, Nuxt 3 (via Vite) e Qwik City: o mesmo padrão.
Builds de produção são outra história
Tudo acima é config do servidor de desenvolvimento. Quando você roda vite preview ou serve um bundle compilado, nada disso importa porque não há HMR nem middleware de checagem de host. O túnel está apenas encaminhando arquivos estáticos nesse ponto.
Algumas coisas menores
- CORS de host estrito. Se seu frontend acessa uma API em outra origem (outra porta de localhost, ou um túnel de API separado), configure CORS no lado da API. O Vite não faz proxy por padrão a menos que você diga via
server.proxy. - Apps com muito WebSocket. O HMR do Vite é um WebSocket. Se seu app usa outro WebSocket para estado de jogo, chat ou dados ao vivo, esse é separado e também precisa ser configurado no seu código cliente para usar a URL do túnel.
- URL do túnel em arquivos env. Colocamos a URL do túnel ativo no
.env.localcomoVITE_PUBLIC_URLquando o frontend precisa saber o próprio endereço público.import.meta.env.VITE_PUBLIC_URLa lê no cliente.
Passo a passo
- Adicione o bloco
server.host,allowedHostsehmrà sua config do Vite. - Reinicie o servidor de desenvolvimento.
- Execute
npx portpreview 5173(ou a porta que o Vite usa). - Abra a URL HTTPS no navegador ou num celular.
- Edite um componente. Confirme que o HMR atualiza a página sem recarga completa.
Se o HMR não atualizar, abra o console do navegador — o Vite registra a tentativa de conexão WebSocket e você verá a URL que ele tentou. Isso diz se a mudança de hmr.clientPort/protocol surtiu efeito.
Entre na lista de espera do PortPreview para túneis que preservam upgrades de WebSocket por padrão.