12k
All articles

Substitua Números de Porta por URLs Nomeadas em Desenvolvimento

Substitua portas de localhost por URLs nomeadas com .localhost, proxy reverso ou portless para evitar conflitos, vazamento de cookies e abas erradas.

OpenReplay Team
OpenReplay Team
Substitua Números de Porta por URLs Nomeadas em Desenvolvimento

Um nome de domínio localhost é um hostname legível por humanos, como app.localhost, que resolve para 127.0.0.1, permitindo que cada serviço local mantenha um endereço estável em vez de um número de porta que muda constantemente.

Você provavelmente já viveu aquele momento em que três dev servers estão rodando, você volta para a aba localhost:3000 para conferir uma correção e a aplicação que aparece na tela é o projeto de ontem. Trocar localhost:3000 por app.localhost resolve de uma só vez um conjunto de incômodos diários (conflitos de porta, URLs que mudam, vazamento de cookies e o problema da “aba errada”), porque cada aplicação ganha seu próprio hostname e, com ele, seu próprio escopo isolado no navegador. Este artigo aborda três caminhos para chegar lá: o TLD .localhost nativo, um reverse proxy montado por você mesmo e o portless, um proxy local desenvolvido especificamente para isso pelo Vercel Labs.

Principais Pontos

  • O TLD .localhost é reservado para uso em loopback pela RFC 6761, então qualquer nome sob ele resolve para 127.0.0.1 no Chrome, Firefox e Edge sem precisar de entrada no arquivo hosts. O Safari, que delega ao resolvedor do sistema, ainda pode precisar de uma.
  • Como os navegadores delimitam o escopo dos cookies por host e ignoram a porta, app.localhost e api.localhost permanecem separados, enquanto localhost:3000 e localhost:3001 compartilham o mesmo cookie jar.
  • O TLD .localhost sozinho não elimina a porta; sua aplicação continua escutando em uma, então você precisa de um reverse proxy para mapear o hostname até essa porta.
  • O portless (Vercel Labs, ainda pré-1.0) atribui a cada aplicação uma porta efêmera na faixa 4000–4999 por meio da variável de ambiente PORT e roteia uma URL estável name.localhost para ela, com HTTPS e HTTP/2 ativados por padrão.
  • Uma URL nomeada estável registrada em um arquivo de agentes permite que ferramentas de codificação com IA acessem o serviço correto em vez de adivinhar entre a porta 3001 e a 8080.

Por que URLs nomeadas são melhores que números de porta?

O desenvolvimento local baseado em portas falha de maneiras previsíveis assim que você roda mais de um serviço. Inicie uma segunda aplicação em uma porta ocupada e o Node lança EADDRINUSE. Frameworks que incrementam automaticamente evitam o crash, mas introduzem instabilidade: seu blog está em localhost:3001 hoje e em localhost:3002 amanhã, então os bookmarks apodrecem e o histórico do navegador para localhost:3000 vira uma pilha impossível de navegar, cheia de projetos sem relação entre si. Encerre um servidor, inicie outro na porta liberada, e uma aba que você deixou aberta passa silenciosamente a servir o outro projeto: o problema da “aba errada”.

A falha mais sutil é o vazamento de estado. Os navegadores delimitam o escopo dos cookies por host e desconsideram a porta, então localhost:3000 e localhost:3001 gravam no mesmo cookie jar. O estado de sessão de uma aplicação vaza para outra. Subdomínios nomeados resolvem isso no nível da origem: app.localhost e api.localhost são hostnames distintos, então separam os cookies de forma limpa e, como a same-origin policy se baseia em esquema, host e porta, também separam localStorage e sessionStorage. A orientação da Microsoft sobre o TLD faz o mesmo argumento: dar a cada aplicação local seu próprio nome mantém separados os recursos cujo escopo depende do nome, como cookies, e o nome na barra de endereços informa de imediato qual aplicação você está vendo.

O que é o TLD .localhost?

O mecanismo mais simples de URL nomeada já vem no seu navegador. A RFC 6761 reserva o TLD .localhost, e todo nome abaixo dele, para o endereço de loopback, e é por isso que app.localhost responde em 127.0.0.1 sem nenhuma configuração. Chrome, Firefox e Edge tratam essa resolução internamente, mapeando qualquer nome *.localhost para 127.0.0.1 ou ::1, de modo que esse nome funciona como um alias para o que já estiver sendo servido em localhost. O Safari é o ponto de atenção: ele repassa o nome ao resolvedor DNS do sistema, e nem toda configuração de resolvedor responde por subdomínios .localhost, então você pode precisar de uma entrada em /etc/hosts nesse caso.

Há um porém: o TLD sozinho não elimina a porta. Sua aplicação continua escutando em :3000, e app.localhost sem porta simplesmente acessa app.localhost:80, onde não há nada escutando. Para realmente eliminar o número, você precisa de um reverse proxy na porta 80 ou 443 que leia o header Host e encaminhe para a porta real da aplicação.

DIY: arquivo hosts mais um reverse proxy

Você pode montar URLs nomeadas a partir de peças que já conhece. Adicione um hostname a /etc/hosts (ou confie na resolução automática de .localhost) e então rode um reverse proxy que mapeie o nome para a porta do seu dev server. Uma configuração do Caddy é praticamente o mais conciso possível:

app.localhost {
  reverse_proxy localhost:3000
}
api.localhost {
  reverse_proxy localhost:8080
}

O Caddy provisiona certificados TLS locais automaticamente; nginx e Traefik fazem o mesmo trabalho com mais configuração. Para domínios locais com wildcard, o dnsmasq pode resolver todo um espaço *.test para 127.0.0.1, dispensando entradas de hosts nome a nome. E cada dev server ainda precisa ter host e porta fixados (Vite via server.host e server.port, webpack via devServer) para que o proxy tenha um alvo estável.

A contrapartida é a manutenção. Você mantém a configuração do proxy, a confiança nos certificados, as entradas do hosts e as atribuições de porta por projeto, e sincroniza esses quatro itens manualmente conforme os serviços vão e vêm. Para uma ou duas aplicações de longa duração, tudo bem. Em um monorepo, isso vira uma tarefa por si só.

portless: URLs nomeadas que simplesmente funcionam

O portless é um proxy local que automatiza toda essa cadeia. Você prefixa seu comando de desenvolvimento, de modo que next dev vira portless run next dev, ou executa apenas portless e deixa que ele infira o nome da aplicação a partir do package.json, da raiz do git ou do diretório. O proxy inicia automaticamente, atribui uma porta livre na faixa 4000–4999, a injeta pela variável de ambiente PORT e roteia https://name.localhost para ela. Frameworks que ignoram PORT, como Vite, Astro, Angular e Expo, recebem a flag --port correta em seu lugar, além de uma flag --host correspondente quando necessária.

Nos releases 0.15.x, o portless habilita HTTPS com HTTP/2 por padrão na porta 443, gerando e confiando em uma autoridade certificadora local na primeira execução. Ele se auto-eleva com sudo no macOS e no Linux, porque fazer bind na 443 exige root, e portless trust readiciona a CA caso você tenha pulado o prompt. Textos anteriores que mostram uma flag opcional --https e um padrão :1355 descrevem uma versão já superada. O HTTP/2 ajuda localmente por um motivo específico: um navegador mantém apenas seis conexões HTTP/1.1 abertas para um mesmo host, então um dev server que entrega centenas de arquivos separados sem bundle acaba enfileirando-os, enquanto uma única conexão HTTP/2 carrega todos de uma vez. O portless exige Node.js 24 ou superior.

Alguns recursos justificam sua existência em configurações maiores. Subdomínios como api.myapp.localhost organizam microsserviços; um único portless.json na raiz de um monorepo descobre automaticamente os pacotes do workspace. Para um serviço com porta fixa que você não pode alterar, como um container Docker, portless alias <name> <port> mapeia uma URL nomeada para ele, e PORTLESS=0 contorna o proxy por completo para CI ou um teste rápido. Se você quiser um TLD personalizado, o portless recomenda .test, que a RFC 6761 também reserva, e desaconselha outros dois: .local conflita com mDNS e Bonjour, enquanto .dev pertence ao Google, que o força a fazer upgrade para HTTPS via HSTS.

Por que URLs locais estáveis importam para agentes de codificação com IA

Agentes de codificação com IA falham com portas do mesmo jeito que humanos, só que silenciosamente: eles fixam no código um número visto anteriormente no contexto ou chutam errado. Um agente que lê um https://api.myapp.localhost fixo de um arquivo AGENTS.md sempre aponta para o serviço correto, em vez de alternar entre 3001 e 8080 entre sessões e interromper você para perguntar. Trata-se de uma mudança mais ampla no ferramental de desenvolvimento: endpoints estáveis são infraestrutura para automação. O portless distribui skill files, e os releases 0.15.x adicionam páginas de documentação em Markdown e um índice llms.txt, para tornar suas URLs descobríveis por agentes sem configuração adicional.

Escolhendo uma abordagem

Apenas TLD .localhostTLD + reverse proxyportless
Elimina a porta?NãoSimSim
Ferramentas adicionaisNenhumaCaddy/nginx/TraefikUma instalação global
HTTPSManualFornecido pelo proxyAtivado por padrão
Descoberta automática em monorepoNãoNãoSim
Amigável a agentesParcialParcialSim (skill files, llms.txt)
Atrito de configuraçãoMínimoMédio (sincronização manual)Baixo

A decisão em uma linha: opte pelo TLD nativo mais um reverse proxy se você quiser zero ferramentas novas e não se importar em manter configuração; opte pelo portless se quiser que URLs nomeadas simplesmente funcionem em muitos serviços, em um monorepo ou com agentes de IA.

URLs locais nomeadas, estáveis e legíveis por humanos são estritamente melhores que números de porta, e você pode adotá-las em minutos: adicione um Caddyfile de duas linhas hoje, ou prefixe um script de desenvolvimento com portless e nunca mais pense em EADDRINUSE.

Perguntas Frequentes

Preciso adicionar subdomínios .localhost ao meu arquivo /etc/hosts?

Não, não no Chrome, Firefox ou Edge. Esses três resolvem por conta própria qualquer nome sob o TLD .localhost para 127.0.0.1, porque a RFC 6761 reserva o TLD para uso em loopback, então app.localhost e api.localhost funcionam sem nenhuma configuração. O Safari é a exceção, porque repassa a busca ao resolvedor DNS do sistema, e nem toda configuração de resolvedor responde por subdomínios .localhost. Adicione uma entrada em /etc/hosts nesse caso, se algum nome não carregar.

Usar um domínio .localhost elimina o número de porta do meu dev server?

Não. O TLD .localhost apenas resolve o hostname para 127.0.0.1; sua aplicação continua escutando na porta original, então app.localhost sem porta acessa app.localhost:80, onde não há nada rodando. Para realmente eliminar o número, você precisa de um reverse proxy na porta 80 ou 443 que leia o header Host e encaminhe para a porta real da aplicação. É exatamente isso que ferramentas como Caddy ou portless automatizam.

Por que os cookies vazam entre localhost:3000 e localhost:3001, mas não entre app.localhost e api.localhost?

Os navegadores delimitam o escopo dos cookies por host e ignoram a porta, então localhost:3000 e localhost:3001 compartilham o mesmo host, localhost, e portanto o mesmo cookie jar. Subdomínios nomeados têm hosts distintos, então app.localhost e api.localhost mantêm cookies separados. Como a same-origin policy se baseia em esquema, host e porta, hostnames distintos também separam de forma limpa localStorage e sessionStorage, o que origens baseadas em porta não fazem.

Qual versão do Node.js o portless exige, e ele funciona sem sudo?

O portless exige Node.js 24 ou superior. No macOS e no Linux, ele se auto-eleva com sudo na primeira execução, porque fazer bind na porta 443 para HTTPS requer privilégios de root. O HTTPS roda com HTTP/2 desde o início, e o portless cria e passa a confiar em uma autoridade certificadora local na primeira vez em que é executado; use portless trust para adicionar a CA depois, caso tenha pulado o prompt inicial.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.