Localhost è il nome che il vostro computer usa per sé stesso. Digitatelo in un browser e la richiesta non raggiunge mai una rete: esce dal browser, gira su sé stessa dentro la macchina, e arriva a qualunque cosa sia in ascolto su quella porta qualche microsecondo dopo.

Questa è tutta la definizione, ed è davvero semplice. Quello che non è semplice è che il browser tratta questo indirizzo diversamente da ogni altro di proposito, in più modi alla volta. Quindi una pagina servita da localhost non è la stessa pagina servita da un dominio, e negli spazi fra le due vive un tipo preciso di bug: quello invisibile fino al momento in cui fate il deploy.

localhost, 127.0.0.1, e il terzo che nessuno nomina

Vengono usati come sinonimi e non sono la stessa cosa.

127.0.0.1
Un indirizzo. Il loopback IPv4, cablato per significare questa macchina. Nessuna risoluzione di nomi di mezzo
::1
La stessa idea in IPv6, e un indirizzo diverso. Un server legato solo a IPv4 qui non è in ascolto
localhost
Un nome che si risolve in uno dei due. Di solito in entrambi, e l'ordine lo sceglie la macchina
0.0.0.0
Non un indirizzo da visitare. Significa "ascolta su tutte le interfacce", ed è ciò che rende un server raggiungibile dal telefono sulla stessa wifi

Quella terza riga è all’origine di un pomeriggio preciso e logorante. localhost è un hostname, e la macchina lo risolve. Su un sistema che preferisce IPv6, localhost diventa ::1, e un server legato solo a 127.0.0.1 non è in ascolto su ::1. Il risultato è una connessione rifiutata su localhost e una pagina perfettamente funzionante su 127.0.0.1, il che si legge come se la macchina si contraddicesse. Non è così. Sono due indirizzi e il vostro server sta su uno dei due.

Perché il browser qui piega le proprie regole

Questa è la parte che conta per i bug, e la maggior parte delle spiegazioni su localhost la tralascia del tutto.

I browser richiedono un contesto sicuro per una lunga lista di funzionalità: service worker, l’API degli appunti, la geolocalizzazione, fotocamera e microfono, le notifiche e altro. Contesto sicuro normalmente significa HTTPS. Ma localhost è considerato potenzialmente affidabile e ottiene l’eccezione, perché il traffico non lascia mai la macchina e non c’è nulla in mezzo a intercettarlo.

Il ragionamento è solido e la conseguenza è una trappola. Tutto ciò che sta in quella lista funziona su http://localhost e smette di funzionare nel momento in cui lo stesso codice viene servito da http://vostra-macchina-di-staging su HTTP semplice. Niente nella vostra esecuzione locale vi ha detto che la funzionalità dipendeva da un contesto sicuro, perché la regola per voi è stata sospesa in locale e viene applicata ovunque altro.

Le altre differenze, che puntano tutte nella stessa direzione

Localhost non è una versione piccola della produzione. È un ambiente diverso che per caso esegue lo stesso codice, e quasi ogni differenza vi lusinga.

Non c’è rete. Nessuna latenza, nessuna perdita di pacchetti, nessuna wifi ballerina. Ogni race condition che dipende dall’arrivo di una richiesta dopo un’altra si risolve in locale dal lato veloce e dall’altro lato per chi è su un treno. Gli indicatori di caricamento che nessuno vede mai di solito sono questo.

Non c’è CDN, né proxy, né load balancer. La compressione, la cache, la riscrittura degli header e il buffering delle richieste che stanno davanti alla produzione mancano tutti. Una risposta che funziona in locale può essere trasformata da qualcosa nel mezzo prima che un utente vero la veda.

Il filesystem probabilmente non distingue le maiuscole. Su macOS e Windows, Logo.svg e logo.svg sono lo stesso file. Sulla macchina Linux su cui fate il deploy no, e l’import che funzionava sul computer di ogni sviluppatore dà 404 in produzione.

I cookie si comportano diversamente. localhost è trattato come caso speciale dai browser per i cookie sicuri, e non ha un dominio registrabile, quindi SameSite e il comportamento fra sottodomini che in locale non esercitate mai viene esercitato subito in produzione.

Siete un utente solo. Nessuna concorrenza, nessun pool di connessioni sotto pressione, nessuna cache che un’altra richiesta abbia già scaldato.

Ognuna di queste rende l’esecuzione locale più facile di quella vera. È lo schema che vale la pena notare: localhost non fallisce in modo diverso, fallisce di meno, ed è esattamente questo a fare di “funziona in localhost” un segnale debole invece che una rassicurazione.

Cosa significa davvero quella frase

Quando qualcuno dice che una cosa funziona in locale e non in produzione, non ha fatto un’affermazione sul codice. Ha fatto un’affermazione su due ambienti, e la domanda utile è quale differenza ne sia responsabile.

È una lista corta, ed è la stessa ogni volta: l’eccezione del contesto sicuro, l’assenza di rete, l’assenza delle scatole intermedie, le maiuscole nei nomi dei file, l’ambito dei cookie, e il carico. Un bug che compare al deploy e non in locale è quasi sempre uno di questi sei, e conoscerli trasforma “sul mio computer funziona” da accusa in lista di controllo.

È anche il motivo per cui esiste un ambiente di staging, e per cui nemmeno quello è mai del tutto una copia.

Scoprire quale fosse

La difficoltà di un bug d’ambiente è che non potete vederlo dall’ambiente in cui siete. La persona che lo incontra sta dall’altra parte, con una pagina che non funziona e nessun modo di dirvi perché, e la vostra macchina continua a insistere che va tutto bene.

Session Replay

Estensione Chrome gratuita. Un clic sulla pagina che si comporta male cattura lo screenshot, la console e il log di rete, e vi dà un link da incollare nel ticket.

Ottieni l'estensione

La console è dove un fallimento del contesto sicuro si annuncia, e il log di rete è dove compaiono la riscrittura di un proxy, un 404 su un file che in locale esiste, o un cookie mai inviato. Catturati dalla macchina dove si è rotto davvero, quei due rispondono alla domanda a cui la vostra macchina non può rispondere.

In un paragrafo

Localhost è il nome che il vostro computer dà a sé stesso, che si risolve in 127.0.0.1 o ::1, e un server legato a uno dei due non è in ascolto sull’altro: è tutta qui la spiegazione del fatto che localhost rifiuti una connessione che 127.0.0.1 accetta. Più importante ancora, i browser lo trattano come contesto sicuro anche su HTTP semplice, quindi service worker, appunti, geolocalizzazione e fotocamera funzionano in locale e possono smettere di funzionare nel momento in cui lo stesso codice viene servito su HTTP da qualunque altro posto. Aggiungete la rete assente, i proxy e la CDN assenti, un filesystem che non distingue le maiuscole, un ambito dei cookie diverso e un carico di esattamente uno, e localhost non è una produzione in piccolo: è un ambiente che fallisce di meno, ed è per questo che “funziona in localhost” da solo non restringe nulla.