Localhost - это имя, которым ваш компьютер называет сам себя. Наберите его в браузере, и запрос никогда не дойдёт до сети: он выйдет из браузера, развернётся внутри машины и через несколько микросекунд придёт к тому, что слушает этот порт.

Вот и всё определение, и оно действительно простое. Непростое в том, что браузер намеренно обращается с этим адресом иначе, чем со всеми остальными, и сразу по нескольким пунктам. Поэтому страница, отданная с localhost, - это не та же самая страница, отданная с домена, а в зазорах между ними живёт особый сорт багов: тот, что невидим ровно до момента выкатки.

localhost, 127.0.0.1 и третье, о котором никто не упоминает

Их используют как синонимы, и это не одно и то же.

127.0.0.1
Адрес. Петлевой адрес IPv4, жёстко означающий "эта машина". Никакого разрешения имён
::1
Та же идея в IPv6 и другой адрес. Сервер, привязанный только к IPv4, здесь не слушает
localhost
Имя, которое разрешается в один из двух. Обычно в оба, и порядок выбирает машина
0.0.0.0
Не адрес, по которому ходят. Это значит "слушай на всех интерфейсах", и именно это делает сервер доступным с телефона в той же сети

Третья строка - источник одного вполне определённого изматывающего дня. localhost - это имя хоста, и машина его разрешает. В системе, предпочитающей IPv6, localhost превращается в ::1, а сервер, привязанный только к 127.0.0.1, на ::1 не слушает. В итоге на localhost соединение отклоняется, а на 127.0.0.1 страница прекрасно работает, и это читается так, будто машина противоречит сама себе. Не противоречит. Это два адреса, и ваш сервер на одном из них.

Почему браузер здесь гнёт собственные правила

Это та часть, которая важна для багов, и большинство объяснений про localhost её вовсе опускает.

Браузеры требуют безопасного контекста для длинного списка возможностей: service worker, API буфера обмена, геолокация, камера и микрофон, уведомления и не только. Безопасный контекст обычно означает HTTPS. Но localhost считается потенциально заслуживающим доверия и получает исключение, потому что трафик никогда не покидает машину и между ними нет ничего, что могло бы его перехватить.

Рассуждение здравое, а последствие - ловушка. Всё из этого списка работает на http://localhost и перестаёт работать в тот момент, когда тот же код отдаётся с http://ваш-стейджинг по обычному HTTP. Ничто в вашем локальном запуске не сказало вам, что функция зависит от безопасного контекста, потому что для вас правило локально отменили, а везде остальное применяют.

Остальные различия, и все они указывают в одну сторону

Localhost - это не уменьшенный продакшен. Это другая среда, в которой по случайности выполняется тот же код, и почти каждое отличие вам льстит.

Сети нет. Ни задержек, ни потерь пакетов, ни шаткого вайфая. Любая гонка, зависящая от того, что один запрос придёт после другого, локально разрешается быстрым путём, а у человека в поезде - другим. Индикаторы загрузки, которых никто никогда не видит, обычно про это.

Нет CDN, нет прокси, нет балансировщика. Сжатие, кеширование, переписывание заголовков и буферизация запросов, стоящие перед продакшеном, отсутствуют целиком. Ответ, работающий локально, может быть преобразован чем-то посередине, прежде чем его увидит настоящий пользователь.

Файловая система, скорее всего, не различает регистр. На macOS и Windows Logo.svg и logo.svg - один файл. На Linux-машине, куда вы выкатываетесь, - нет, и импорт, работавший на компьютере каждого разработчика, в продакшене даёт 404.

Куки ведут себя иначе. Браузеры обрабатывают localhost особым образом для безопасных куки, и у него нет регистрируемого домена, так что SameSite и поведение между поддоменами, которое вы локально не задействуете никогда, в продакшене задействуется сразу.

Вы - один пользователь. Никакой параллельности, никакого пула соединений под нагрузкой, никакого кеша, который уже прогрел другой запрос.

Каждое из этого делает локальный запуск легче настоящего. Вот та закономерность, которую стоит заметить: localhost падает не иначе, он падает реже, и именно это превращает “у меня на localhost работает” в слабый сигнал, а не в успокоение.

Что эта фраза на самом деле означает

Когда кто-то говорит, что локально работает, а в продакшене нет, он не сделал утверждения о коде. Он сделал утверждение о двух средах, и полезный вопрос - какое именно различие виновато.

Список короткий, и он каждый раз один и тот же: исключение для безопасного контекста, отсутствие сети, отсутствие промежуточных узлов, регистр в именах файлов, область видимости куки и нагрузка. Баг, появляющийся при выкатке и не появляющийся локально, почти всегда один из этих шести, и знание их превращает “на моей машине работает” из обвинения в чек-лист.

Это же и причина, по которой существует стейджинг, и по которой он тоже никогда не бывает точной копией.

Как выяснить, какое именно

Трудность бага среды в том, что его не видно из той среды, в которой находитесь вы. Человек, столкнувшийся с ним, находится по другую сторону, со страницей, которая не работает, и без возможности объяснить вам почему, а ваша машина продолжает настаивать, что всё в порядке.

Session Replay

Бесплатное расширение Chrome. Один клик на странице, которая ведёт себя неправильно, захватывает снимок экрана, консоль и сетевой журнал, и даёт вам ссылку для вставки в задачу.

Получить расширение

Консоль - это место, где о себе заявляет сбой безопасного контекста, а сетевой журнал - место, где всплывают и переписывание прокси, и 404 на файл, который локально есть, и куки, которые так и не отправили. Захваченные на той машине, где всё действительно сломалось, эти двое отвечают на вопрос, на который ваша собственная машина ответить не может.

В одном абзаце

Localhost - это имя, которым ваш компьютер называет сам себя, разрешающееся в 127.0.0.1 или ::1, и сервер, привязанный к одному из них, не слушает на другом, - в этом всё объяснение того, почему localhost отклоняет соединение, которое принимает 127.0.0.1. Важнее другое: браузеры считают его безопасным контекстом даже по обычному HTTP, поэтому service worker, буфер обмена, геолокация и камера работают локально и могут перестать работать в тот момент, когда тот же код начнут отдавать по HTTP откуда-то ещё. Прибавьте отсутствующую сеть, отсутствующие прокси и CDN, файловую систему без различения регистра, другую область видимости куки и нагрузку ровно в единицу, и localhost окажется не маленьким продакшеном, а средой, которая падает реже, - поэтому “у меня на localhost работает” само по себе ничего не сужает.