O localhost é o nome que o seu computador usa para si próprio. Escreva-o num navegador e o pedido nunca chega a uma rede: sai do navegador, dá a volta dentro da máquina, e chega ao que estiver à escuta nessa porta uns microssegundos depois.

É essa toda a definição, e é genuinamente simples. O que não é simples é que o navegador trata este endereço de maneira diferente de todos os outros de propósito, em vários aspectos ao mesmo tempo. Por isso uma página servida a partir do localhost não é a mesma página servida a partir de um domínio, e nos intervalos entre as duas vive um tipo específico de bug: aquele que é invisível até ao momento em que faz o deploy.

localhost, 127.0.0.1, e o terceiro que ninguém menciona

Usam-se indistintamente e não são a mesma coisa.

127.0.0.1
Um endereço. O loopback de IPv4, cablado para significar esta máquina. Sem resolução de nomes pelo meio
::1
A mesma ideia em IPv6, e um endereço diferente. Um servidor ligado apenas a IPv4 não está à escuta aqui
localhost
Um nome que resolve para um dos anteriores. Normalmente para ambos, e a ordem é escolha da máquina
0.0.0.0
Não é um endereço que se visite. Quer dizer "escuta em todas as interfaces", que é o que torna um servidor alcançável a partir do seu telemóvel na mesma wifi

Essa terceira linha é a origem de uma tarde específica e exasperante. localhost é um nome de anfitrião, e a máquina resolve-o. Num sistema que prefere IPv6, localhost passa a ::1, e um servidor ligado apenas a 127.0.0.1 não está à escuta em ::1. O resultado é uma ligação recusada em localhost e uma página a funcionar perfeitamente em 127.0.0.1, o que se lê como se a máquina se contradissesse a si própria. Não se contradiz. São dois endereços e o seu servidor está num deles.

Porque é que o navegador dobra aqui as próprias regras

Esta é a parte que importa para os bugs, e a maioria das explicações sobre o localhost deixa-a de fora por completo.

Os navegadores exigem um contexto seguro para uma longa lista de capacidades: service workers, a API da área de transferência, geolocalização, câmara e microfone, notificações e mais. Contexto seguro normalmente quer dizer HTTPS. Mas o localhost é tratado como potencialmente de confiança e recebe a excepção, porque o tráfego nunca sai da máquina e não há nada pelo meio que o intercepte.

O raciocínio é sólido e a consequência é uma armadilha. Tudo o que está nessa lista funciona em http://localhost e deixa de funcionar no momento em que o mesmo código é servido a partir de http://a-sua-maquina-de-staging por HTTP simples. Nada na sua execução local lhe disse que a funcionalidade dependia de um contexto seguro, porque a regra lhe foi dispensada localmente e é aplicada em todo o resto.

As outras diferenças, que apontam todas no mesmo sentido

O localhost não é uma versão pequena da produção. É um ambiente diferente que por acaso corre o mesmo código, e quase todas as diferenças o lisonjeiam.

Não há rede. Sem latência, sem perda de pacotes, sem wifi instável. Toda a condição de corrida que dependa de um pedido chegar depois de outro resolve-se localmente pelo lado rápido e pelo outro lado para quem vai num comboio. Os indicadores de carregamento que ninguém vê são normalmente isto.

Não há CDN, nem proxy, nem balanceador. A compressão, a cache, a reescrita de cabeçalhos e o armazenamento temporário de pedidos que estão à frente da produção faltam todos. Uma resposta que funciona localmente pode ser transformada por algo do meio antes de um utilizador verdadeiro a ver.

O sistema de ficheiros provavelmente não distingue maiúsculas. Em macOS e Windows, Logo.svg e logo.svg são o mesmo ficheiro. Na máquina Linux para onde faz o deploy não são, e o import que funcionava no computador de todos os programadores dá 404 em produção.

Os cookies comportam-se de outra maneira. O localhost é tratado como caso especial pelos navegadores para cookies seguros, e não tem domínio registável, por isso o SameSite e o comportamento entre subdomínios que nunca exercita localmente é exercitado de imediato em produção.

É um utilizador só. Sem concorrência, sem uma pool de ligações sob pressão, sem uma cache que outro pedido já tenha aquecido.

Cada uma delas torna a execução local mais fácil do que a verdadeira. É esse o padrão que vale a pena notar: o localhost não falha de maneira diferente, falha menos, que é exactamente o que faz de “funciona no localhost” um sinal fraco em vez de uma garantia.

O que a frase quer mesmo dizer

Quando alguém diz que uma coisa funciona localmente e não em produção, não fez uma afirmação sobre o código. Fez uma afirmação sobre dois ambientes, e a pergunta útil é qual das diferenças é a responsável.

É uma lista curta, e é a mesma todas as vezes: a excepção do contexto seguro, a ausência de rede, a ausência das caixas do meio, as maiúsculas nos nomes dos ficheiros, o âmbito dos cookies, e a carga. Um bug que aparece no deploy e não localmente é quase sempre um destes seis, e conhecê-los transforma “no meu computador funciona” de uma acusação numa lista de verificação.

É também por isso que existe um ambiente de staging, e porque também esse nunca é bem uma cópia.

Descobrir qual foi

A dificuldade de um bug de ambiente é que não o consegue ver a partir do ambiente em que está. A pessoa que lhe bate está do outro lado, com uma página que não funciona e sem maneira de lhe dizer porquê, e a sua máquina continua a insistir que está tudo bem.

Session Replay

Extensão gratuita do Chrome. Um clique na página que se está a portar mal captura a captura de ecrã, a consola e o registo de rede, e dá-lhe um link para colar no ticket.

Obter a extensão

A consola é onde uma falha de contexto seguro se anuncia, e o registo de rede é onde aparecem a reescrita de um proxy, um 404 num ficheiro que existe localmente, ou um cookie que nunca foi enviado. Capturados a partir da máquina onde partiu de verdade, esses dois respondem à pergunta a que a sua própria máquina não consegue responder.

Num parágrafo

O localhost é o nome que o seu computador dá a si próprio, resolvendo para 127.0.0.1 ou ::1, e um servidor ligado a um deles não está à escuta no outro, o que é toda a explicação para o localhost recusar uma ligação que o 127.0.0.1 aceita. Mais importante ainda, os navegadores tratam-no como contexto seguro mesmo por HTTP simples, por isso os service workers, a área de transferência, a geolocalização e a câmara funcionam localmente e podem deixar de funcionar assim que o mesmo código for servido por HTTP a partir de qualquer outro sítio. Junte a rede ausente, os proxies e a CDN ausentes, um sistema de ficheiros que não distingue maiúsculas, um âmbito de cookies diferente e uma carga de exactamente um, e o localhost não é uma produção pequena: é um ambiente que falha menos, e é por isso que “funciona no localhost” por si só não estreita nada.