Бесконечный цикл редиректов при деплое SPA почти всегда возникает на стыке двух слоёв: прокси или балансировщик принимает HTTPS, а до сервера приложения запрос доходит уже по HTTP — и тот снова отправляет его на HTTPS. Плюс у SPA своя особенность: сервер должен отдавать index.html на любой маршрут, которого нет на диске. В этом кейсе я переписал конфиг сервера под корректную отдачу статики и SPA-маршрутов, обошёл конфликт зависимостей при сборке и развёл аналитику с полями формы.
Что случилось
Три проблемы на пути к стабильному релизу корпоративного приложения на React и Vite:
- сборка падала с ошибкой
ERESOLVEиз-за конфликта версий бандлера и библиотеки стилей; - при деплое сервер уходил в бесконечный цикл редиректов: маршрутизатор облачного провайдера принимал HTTPS-запрос, передавал его по внутренней сети в конфиг, который снова редиректил на HTTPS;
- система аналитики предупреждала о перехвате функций ввода из-за контролируемых UI-компонентов.
Откуда берётся ERR_TOO_MANY_REDIRECTS
Типичная схема хостинга сегодня: снаружи стоит прокси или балансировщик, он держит SSL-сертификат и принимает HTTPS. Дальше, во внутренней сети, запрос идёт к серверу приложения обычным HTTP. Если на сервере приложения есть правило «если не HTTPS — редирект на HTTPS», он видит HTTP и редиректит. Браузер снова приходит на прокси по HTTPS, прокси снова передаёт HTTP — и так по кругу, пока браузер не сдастся с ERR_TOO_MANY_REDIRECTS.
Как это проверить: посмотрите цепочку редиректов.
# TODO: подставьте свой домен
curl -sIL https://example.com/ | grep -iE "^(HTTP|location)"Если видите одни и те же 301 или 302 на тот же адрес много раз подряд — это оно.
Общее правило: редирект на HTTPS должен делать тот слой, который видит исходный протокол. Если это прокси — убирайте редирект с сервера приложения. Если редирект нужен именно на сервере, проверяйте не сам протокол соединения, а заголовок, который передаёт прокси, обычно X-Forwarded-Proto.
Шаг 1. Конфиг сервера для SPA
Я переписал конфиг сервера, чтобы статика и маршруты приложения отдавались корректно:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>Что делает каждая строка: запрос к самому index.html отдаётся как есть; если запрошенного файла или каталога на диске нет — отдаётся index.html, а маршрут разбирает уже роутер приложения. Без этого правила прямой заход на /about или обновление страницы внутри приложения дают 404.
Ещё одна деталь из этого деплоя: после правок пришлось очистить кеш локальной сети, чтобы ушли фантомные ошибки сертификата — браузер и DNS какое-то время помнили старое состояние.
Шаг 2. Конфликт зависимостей при сборке
ERESOLVE — это npm сообщает, что две библиотеки требуют несовместимые версии общей зависимости. Здесь конфликтовали бандлер и библиотека стилей. Я поставил зависимости в обход строгой проверки версий:
npm install -D tailwindcss @tailwindcss/vite --legacy-peer-deps--legacy-peer-deps — рабочий обходной путь, но не бесплатный: npm перестаёт проверять совместимость peer-зависимостей, и несовместимость может всплыть уже в рантайме. После такой установки обязательно прогоните сборку и пройдитесь по приложению руками. А когда выйдут версии, которые дружат между собой, — уберите флаг.
Шаг 3. Аналитика и поля формы
Система аналитики предупреждала о перехвате функций ввода: её запись действий пользователя конфликтовала с контролируемыми полями React. Я добавил в разметку формы блокирующий атрибут, чтобы аналитика не трогала ввод:
<form data-ym-disable-keys="true">Побочный плюс — содержимое полей формы не попадает в записи сессий, а для форм с контактами это правильно и с точки зрения персональных данных.
Результат
- Оценка PageSpeed достигла 98 баллов
- Время до первого взаимодействия 1.2 секунды
- Конверсия захвата формы +14%
- Ошибки редиректов полностью устранены
Чек-лист деплоя SPA
- Сервер отдаёт
index.htmlна все маршруты, которых нет на диске, а статику — напрямую. - Редирект на HTTPS делает ровно один слой — тот, что видит исходный протокол.
- Цепочка редиректов проверена через
curl -sIL: не больше одного перехода. - Сборка проходит без
--force, или вы понимаете, почему флаг нужен, и проверили приложение руками. - Скрипты аналитики не ломают контролируемые поля форм и не пишут их содержимое.
FAQ
Почему при деплое SPA возникает бесконечный редирект?
Обычно прокси принимает HTTPS и передаёт запрос серверу по HTTP, а сервер снова редиректит на HTTPS. Редирект должен делать один слой — тот, что видит исходный протокол, либо сервер должен проверять заголовок X-Forwarded-Proto.
Почему SPA отдаёт 404 при обновлении страницы?
Сервер ищет файл по пути маршрута и не находит его. Нужно правило, которое отдаёт index.html на любые пути, которых нет на диске.
Что значит ошибка ERESOLVE в npm?
Две зависимости требуют несовместимые версии общей библиотеки. Флаг --legacy-peer-deps позволяет установить их, но совместимость тогда нужно проверить вручную.