Редирект вёл на 0.0.0.0:3000, а локально всё работало
Абсолютный адрес строился от того, что процесс думает о себе. За nginx это дало адрес, которого не существует, — и ни одна проверка до выката этого не видела.
Классика, которая всё равно ловит. В 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.
Читайте также
Автор
Александр ЦапковОснователь Скоупворк
Веду платформу и её боевой контур сам: разработка, выкат, дежурство. Пишу о том, на чём мы обожглись, — с датами, замерами и ссылками на решения в репозитории.