Копии и выкат

Редирект вёл на 0.0.0.0:3000, а локально всё работало

Абсолютный адрес строился от того, что процесс думает о себе. За nginx это дало адрес, которого не существует, — и ни одна проверка до выката этого не видела.

Александр Цапков10 сентября 20262 мин

Классика, которая всё равно ловит. В Next.js за прокси удобно строить абсолютный адрес редиректа от req.nextUrl.origin: локально это http://localhost:3000, и всё правильно.

На бою за nginx та же строка отдала http://0.0.0.0:3000 — адрес, на котором слушает сам процесс, а не домен, по которому пришёл человек. Пользователь после действия улетал на адрес, которого не существует.

Почему локально этого не видно

nextUrl.origin собирается из того, что процесс знает о своём сокете. Без прокси это ровно тот адрес, по которому пришёл запрос, — значение верное, и проверка проходит.

За прокси связь рвётся: снаружи человек пришёл на https://example.com, до процесса запрос дошёл на 0.0.0.0:3000, и процесс честно назвал второе. Ошибки нет ни в одной строке кода — есть предположение, которое перестало быть верным при смене окружения.

Ни одна проверка до выката этого не поймает по построению: локально прокси нет, а значит, нет и расхождения.

Что мы сделали

Абсолютные адреса на домен строятся от константы — адреса приложения из настроек, — а не от того, что процесс думает о себе.

Request-derived origin остался ровно там, где он и нужен: для относительных переходов внутри той же страницы, где домен вообще не участвует.

Граница простая и её легко держать в голове:

Куда ведёт ссылкаОткуда берётся адрес
наружу, в письмо, в редирект на доменконстанта из настроек
внутри той же страницы, относительный переходиз запроса

Как проверить у себя

Сначала — есть ли у вас это вообще:

grep -rn "nextUrl.origin" src/

Если в ответе редиректы или сборка адресов для писем — знаете, что делать.

А после выката проверка идёт фактом, а не чтением кода:

curl -sI https://example.com/ваша-ручка-с-редиректом | grep -i '^location:'

Десять секунд. В тот раз они сэкономили бы час.

Что из этого следует

Это частный случай более общего: процесс не знает, по какому адресу к нему пришли. Он знает, на каком сокете слушает. Всё, что снаружи, — домен, схема, порт — доезжает до него только заголовками, и то если прокси их поставил.

Отсюда правило, которое мы применяем шире одного origin: всё, что уедет за пределы процесса — адрес в письме, ссылка в уведомлении, редирект на домен, — строится из настроек, а не выводится из запроса. Запрос отвечает на вопрос «что попросили», а не «как нас зовут снаружи».

И второе, про проверки: если поведение зависит от окружения, его проверяют в окружении. Не тестом, который до прокси не доходит, а одним curl после выката — по заголовку, глазами.


Мы выкатываем приложения на серверы клиентов и разбираем такие случаи на своём контуре первыми. Как устроен выкат — Деплой из Git.

Деплой из Git на свои серверыВыкат из git на свои серверы: домены, сертификаты, проверка здоровья, откат одной кнопкой.

Читайте также

Автор

Александр ЦапковОснователь Скоупворк

Веду платформу и её боевой контур сам: разработка, выкат, дежурство. Пишу о том, на чём мы обожглись, — с датами, замерами и ссылками на решения в репозитории.