Ошибки CORS после развертывания: реальные причины и способы исправления
Ошибки CORS после развертывания обычно вызваны сбоями preflight, неверными настройками credentials или универсальными origin. Узнайте повторяемый чек‑лист по исправлению.

Почему CORS может внезапно ломаться после развертывания
CORS — это правило браузера, а не сервера. Ваш фронтенд на JavaScript выполняется в вкладке браузера, и именно браузер решает, разрешено ли читать ответ с другого origin (домен, протокол или порт). Если бэкенд не возвращает нужные CORS-заголовки, браузер блокирует ответ, даже если сервер реально отправил данные.
Именно поэтому ошибки CORS после развертывания кажутся запутанными: в коде «ничего не поменялось», но origin изменился. Локально вы могли вызывать http://localhost:5173 к http://localhost:3000. После деплоя это превращается во что-то вроде https://app.yourdomain.com, делающего запрос к https://api.yourdomain.com. Разные домены, HTTPS и иногда другие порты — это другой origin, и браузер повторно проверяет разрешения.
Типичные симптомы выглядят примерно так:
- “Blocked by CORS policy”, хотя API работает в Postman или curl
- Предварительный (OPTIONS) запрос падает до того, как выполнится основной запрос
- CORS-заголовки отсутствуют при ошибках (401/403/500), поэтому проблема проявляется только при сбоях
- Запросы с cookie внезапно перестают работать
Развертывание добавляет невидимые звенья. Реверс-прокси, CDN или настройки платформы могут переопределять заголовки, удалять их или кешировать неправильную версию. Переход на HTTPS может изменить поведение cookie (SameSite и Secure), что повлияет на запросы с credentials.
Реалистичный пример: в продакшне ваш фронтенд начинает слать fetch(..., { credentials: 'include' }) чтобы удержать сессию. Если бэкенд отвечает Access-Control-Allow-Origin: *, браузер отвергнет ответ, потому что подстановочный origin нельзя использовать с credentials.
Если вы унаследовали AI-сгенерированный фронтенд/бэкенд, такие несоответствия часты. В FixMyMess быстрое решение обычно — убедиться в том, какой origin реально используется в продакшне, и затем гарантировать, что на всех путях ответа (включая ошибки) возвращаются согласованные CORS-заголовки.
Preflight 101: OPTIONS‑запрос, который вы могли не заметить
Многие CORS-ошибки после развертывания вовсе не связаны с основным API-вызовом. Браузер часто сначала отправляет тихий «preflight» запрос. Это OPTIONS-запрос, который спрашивает сервер: «Если я отправлю этот реальный запрос с этого origin, вы позволите?»
Простые и предварительные запросы
Некоторые запросы «простые» и пропускают preflight. Многие современные фронтенд-вызовы не являются простыми, поэтому браузер делает preflight.
Preflight срабатывает при таких вещах, как:
- Методы, отличные от GET/POST/HEAD (например PUT, PATCH, DELETE)
- Отправка JSON с
Content-Type: application/json - Любой кастомный заголовок (например
Authorization,X-Requested-WithилиX-Api-Key) - Использование опций
fetch, которые добавляют заголовки, которых вы не ожидали
Это значит, что вы можете напрямую попасть на конечную точку и увидеть «200 OK», но браузер всё равно заблокирует вызов. Почему? Потому что браузер не прошёл шаг OPTIONS. Если OPTIONS возвращает 404, 401, 500 или отсутствуют CORS-заголовки, реальный запрос не будет отправлен.
Почему редиректы часто ломают preflight
Типичная ошибка, проявляющаяся только в продакшне, — неожиданный редирект на OPTIONS. Например, API может перенаправлять HTTP на HTTPS, добавлять или убирать «www» или отправлять неаутентифицированные запросы на страницу логина. Браузеры плохо обрабатывают редиректы при preflight, и многие будут считать это ошибкой, даже если перенаправленный URL работал бы.
Практический пример: фронтенд на https://app.example.com делает запрос к https://api.example.com. API в порядке, но OPTIONS к /v1/data возвращает 301 на /v1/data/ (трейлинг-слэш). GET может успешно пройти в тестах, но браузер блокирует его, потому что preflight не получил корректного CORS-одобренного ответа.
Обычно решение: убедиться, что OPTIONS возвращает прямой успех (часто 204 или 200), включает те же CORS-заголовки, что и реальные ответы, и никогда не делает редиректы.
Origin: что должно совпадать и что чаще путают
Многие ошибки CORS после развертывания появляются потому, что разрешённый origin, который вы настроили, не совпадает со строкой, которую отправляет браузер.
Ключевая путаница — Origin vs Host. Сервер получает заголовок Host (куда идёт запрос), а браузер отправляет заголовок Origin (откуда сделана страница). Решения по CORS зависят от Origin, а не от Host.
Origin — это не просто домен. Это точное трио: схема + домен + порт.
Точное совпадение origin (схема, домен, порт)
Если ваш фронтенд расположен на https://app.example.com, а API на https://api.example.com, браузер отправит Origin: https://app.example.com. Ваш бэкенд должен ответить Access-Control-Allow-Origin: https://app.example.com (или серверная белая книга, которая возвращает именно это значение).
http vs https — классическая ловушка при деплое. Локально вы могли тестировать с http://localhost:3000. После развертывания сайт становится https://..., а в списке разрешённых осталась http-версия — браузер блокирует запрос.
Частые несоответствия, которые нужно искать
Проверьте следующие мелкие различия, которые ломают CORS:
https://example.comvshttps://www.example.com- Отсутствие порта:
https://example.comvshttps://example.com:8443 - Стейджинг-домены:
https://staging.example.comvshttps://app.example.com - Неожиданные локальные origin:
http://127.0.0.1:3000vshttp://localhost:3000 - Мобильные или preview-сборки, использующие другой домен, чем продакшн
Реальный сценарий: фронтенд раздаётся с https://www.brand.com, а в allowlist добавлен только https://brand.com. Для человека выглядит одинаково, но для браузера это разные origin.
Если вы унаследовали AI-сгенерированный бэкенд, в нём часто жёстко закодирован список origin, который не обновили для живого домена. FixMyMess обычно начинает с чтения реальных Origin из логов продакшна и приведения allowlist к этим точным строкам.
CORS‑заголовки ответа, которые должны быть корректны всегда
CORS — это не однократная настройка. Браузер проверяет конкретные заголовки ответа при каждом кросс‑origin запросе и очень строг в этом. Если заголовок отсутствует на одном маршруте (часто на OPTIONS), вы получите CORS-ошибки, которые выглядят случайными.
Браузер в первую очередь хочет знать: разрешён ли мой Origin и для этого типа запроса разрешены метод и заголовки? Это значит, что API должен возвращать Access-Control-Allow-Origin, который соответствует запрашивающему origin (например https://app.example.com). Если вы вернёте неправильный origin или забудете его на некоторых эндпойнтах, браузер заблокирует ответ, даже если сервер вернул 200.
Preflight‑сбои обычно происходят из‑за Access-Control-Allow-Headers. Preflight включает Access-Control-Request-Headers с тем, что браузер планирует отправить. Если в ответе нет каждого из этих заголовков (частые пропуски: Authorization, Content-Type, X-Requested-With), браузер остановит процесс до реального запроса.
Кеширование тоже может делать CORS непредсказуемым. Если вы разрешаете несколько origin, добавьте Vary: Origin, чтобы CDN и прокси не кешировали один и тот же CORS‑ответ для разных origin.
Для OPTIONS preflight возвращать 204 No Content нормально, но заголовки всё равно обязаны быть. Чистый preflight‑ответ обычно содержит:
Access-Control-Allow-OriginAccess-Control-Allow-Methods(включая методы, которые вы будете использовать, напримерGET, POST, PATCH)Access-Control-Allow-Headers(покрывающий то, что запрос запрашивал)Vary: Origin- Необязательно:
Access-Control-Max-Ageчтобы сократить частоту preflight
Практический совет: убедитесь, что CORS‑middleware выполняется до роутинга, проверки аутентификации и обработчиков ошибок. Иначе ответы 401/403/500 могут не получить CORS‑заголовков и вызвать запутанные «CORS blocked» сообщения, которые на самом деле означают проблемы с авторизацией или сервером.
Credentials и подстановочные origin: самая частая продакшн‑ловушка
Многие ошибки CORS после развертывания связаны с правилом, которое узнают только в продакшне: нельзя сочетать credentials и wildcard‑origin.
Если браузер отправляет cookie (или вы указываете ему отправлять credentials), сервер должен возвращать конкретный origin, а не *. Поэтому вот так не сработает:
Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true
Cookie vs Authorization: почему они ведут себя по‑разному
Cookie автоматически прикрепляются браузером, поэтому они под строгим контролем. Если фронтенд и API на разных доменах, cookie не будут отправляться, если явно не включить их на фронтенде и не разрешить на бэкенде.
Заголовок Authorization: Bearer ... — другое дело. Он не опирается на cookie, но часто вызывает preflight, потому что не является простым запросом. Это всё ещё может сломаться, но ловушка «wildcard + credentials» обычно про cookie.
На фронтенде эта одна строчка меняет требования целиком:
fetch("https://api.example.com/me", {
credentials: "include"
})
После добавления credentials: "include" (или withCredentials: true в Axios) браузер отвергнет ответы, если бэкенд не будет предельно явен.
Что бэкенд должен отправлять (всегда)
Убедитесь, что ответы API содержат:
Access-Control-Allow-Origin: https://your-frontend.com(точное совпадение)Access-Control-Allow-Credentials: trueVary: Origin(чтобы кеши не смешивали origin)
Пример: локально вы вызываете http://localhost:3000 к http://localhost:8000 — всё работает. В продакшне вы переключаетесь на https://app.yourdomain.com и начинаете использовать cookie для входа. Если сервер всё ещё отвечает Access-Control-Allow-Origin: *, браузер заблокирует ответ, даже если API вернул 200.
Если вы унаследовали AI‑сгенерированный бэкенд, эта некорректная настройка часта. FixMyMess часто находит, что нужные заголовки есть в одном маршруте, но отсутствуют в обновлении аутентификации или в ошибочных ответах, из‑за чего баг выглядит случайным.
Прокси, CDN и конфигурация платформы, которые переопределяют ваше приложение
Ошибки CORS после развертывания часто возникают потому, что фронтенд больше не общается напрямую с вашим приложением. Реверс‑прокси, балансировщик нагрузки, CDN или «защитный» слой платформы могут менять запросы и ответы так, как локальная среда не делала.
Когда edge меняет ваши заголовки
Многие прокси могут добавлять, удалять или дублировать заголовки. Если приложение возвращает корректный Access-Control-Allow-Origin, но прокси его перезаписывает (или добавляет второй такой заголовок), браузер может отвергнуть ответ. Другая тихая проблема: прокси сжимает или перенаправляет ответы по‑другому, и ответ редиректа лишён CORS‑заголовков, хотя конечная точка правильная.
Типичная продакшн‑ошибка: OPTIONS‑запросы вообще не доходят до приложения. Некоторые платформы считают OPTIONS подозрительными, блокируют их файерволом или направляют на хэндлер по умолчанию, который возвращает 404/405 без CORS‑заголовков. Для браузера это выглядит как «CORS сломался», но на самом деле проблема в маршрутизации.
CDN‑кеш может отдавать неправильные CORS‑заголовки
Если CDN кеширует ответы API без вариации по Origin, он может случайно отдавать ответ с CORS‑заголовками для другого сайта. Пример: пользователь A делает запрос с https://app.example.com и CDN кеширует ответ с разрешённым этим origin. Пользователь B делает тот же запрос с https://admin.example.com и получает закешированные заголовки, не подходящие для его origin — браузер блокирует.
Что проверить по порядку:
- Посмотрите заголовки ответа на уровне edge (CDN/прокси) и на уровне серверного приложения — они должны совпадать.
- Убедитесь, что OPTIONS‑запросы разрешены и маршрутизируются к вашему приложению, а не блокируются или обрабатываются по‑умолчанию.
- Проверьте, что редиректы (http → https, apex → www) тоже возвращают CORS‑заголовки.
- Если есть кеширование, убедитесь, что ответы варьируют по
Originили отключите кеш для API‑маршрутов. - Сравните правила прокси для staging и production построчно.
Если вы унаследовали AI‑сгенерированный бэкенд, несоответствие на уровне proxy/edge — частая причина, которую FixMyMess находит при аудите кода, потому что приложение «выглядит правильно», а конфиг на краю тихо противоречит ему.
Переменные окружения и allowlist доменов: тихие точки отказа
Ошибки CORS после развертывания часто ведут к одной банальной причине: фронтенд теперь делает запрос к другому URL, чем вы думаете. Локально dev‑сервер мог проксировать запросы и скрывать проблему. В продакшне браузер общается напрямую с API, и любое несоответствие сразу проявляется.
Распространённая ошибка — собрать фронтенд с неправильным базовым URL API. Например, билд может подхватить API_URL=https://staging-api... (или старый preview URL), потому что переменная окружения для продакшна не установлена или хостинг закешировал прошлую сборку. Браузер тогда шлёт запросы с вашего живого домена к staging API, который не разрешает этот origin.
Другой тихий провал — allowlist на бэкенде. Команды добавляют http://localhost:3000 во время разработки и забывают добавить реальный домен. Ситуация усугубляется, если у вас много доменов: www vs без www, маркетинговый домен, поддомен приложения и preview‑домены. Если хотя бы один origin отсутствует, соответствующая среда «рандомно» падает.
Чтобы избежать дрейфа, централизуйте разрешённые origin и относитесь к ним как к реальной конфигурации, а не к разбросанным строкам.
Практический способ предотвратить дрейф конфигурации
Держите правила в одном месте и делайте их строгими:
- Используйте одну переменную окружения для разрешённых origin (через запятую), парсите её при старте.
- Нормализуйте домены (включите точную схему и хост:
https://app.example.com). - Поддерживайте отдельные значения для dev, staging и production, и документируйте, какой frontend URL соответствует какому API.
- Логируйте итоговый allowlist при старте (и Origin запросов при ошибках CORS).
- Добавьте быстрый smoke‑тест после деплоя: один API‑вызов из реального домена.
Если вы унаследовали AI‑сгенерированное приложение, это частая диагностика FixMyMess: фронтенд указывает на одну среду, а CORS‑конфиг бэкенда — на другую, и никто этого не замечает до первого деплоя.
Повторяемая стратегия исправления CORS (шаг за шагом)
Когда появляются ошибки CORS после развертывания, относитесь к этому как к задаче отладки, а не к угадыванию. Цель — найти точный запрос, который браузер блокирует, и тот самый заголовок, который отсутствует или отличается.
Начните с вкладки Network в DevTools. Отфильтруйте неудачный API‑запрос и посмотрите, есть ли перед ним OPTIONS. Если видите OPTIONS, вы имеете дело с preflight. Если нет — ошибка обычно связана с реальным запросом (часто отсутствует заголовок в ответе 401/500).
Используйте эту последовательность и не пропускайте шаги:
- Воспроизведите и зафиксируйте неудачный запрос: скопируйте детали из Network (метод, URL, код статуса, и был ли preflight).
- Подтвердите точный Origin: прочитайте заголовок
Originи запишите его точно (схема + домен + порт). Многие «совпадения» на самом деле связаны сhttpvshttpsилиwwwvs безwww. - Проверьте заголовки по обеим ответам: посмотрите ответ OPTIONS и ответ реального запроса. Оба должны содержать нужные CORS‑заголовки (особенно
Access-Control-Allow-Origin, и для cookie:Access-Control-Allow-Credentials). - Устраните редиректы и неожиданные middleware: если API делает редирект (301/302) или принуждает слэш, preflight часто падает, потому что редирект‑ответ не содержит CORS. Сделайте так, чтобы OPTIONS возвращал 200/204 прямо с заголовками.
- Переключитесь с
*на явный allowlist: в продакшне установите короткий список разрешённых origin, протестируйте, затем добавьте только то, что действительно нужно.
Быстрая проверка здравомыслия: если вы используете cookie или auth, нельзя иметь Access-Control-Allow-Origin: *. Нужно эхо‑значение конкретного origin и включённые credentials.
Если бэкенд был сгенерирован AI и логика CORS разбросана по маршрутам, прокси и настройкам платформы, FixMyMess может найти единый источник правды и безопасно его поправить после бесплатного аудита кода.
Частые ошибки, которые держат CORS сломанным
Множество CORS‑проблем после развертывания — это самосозданные ловушки. Локально всё кажется в порядке, потому что вы на одном origin, используете простые запросы, а dev‑сервер прощает многое. В продакшне — строже: разные домены, HTTPS, cookie и иногда прокси или CDN перед приложением.
Вот ошибки, которые чаще всего встречаются, когда фронтенд обращается к отдельному бэкенду:
- Разрешение любых origin во время разработки, а затем включение cookie или авторизации в продакшне без изменения CORS (нельзя использовать
*с credentials). - Добавление CORS‑заголовков только на «счастливом пути», но не на ответах 401/403/500, из‑за чего браузер скрывает реальную ошибку за сообщением CORS.
- Забвение, что браузер шлёт
OPTIONS‑preflight для многих запросов, а сервер/роутер/middleware не отвечает с теми же CORS‑заголовками. - Доверие дефолтам фреймворка (или врезке CORS‑сниппета) без проверки реальных заголовков в продакшне.
- Попытки исправить в фронтенде, меняя настройки fetch/axios, хотя CORS принудительно проверяется браузером и должно быть исправлено на сервере.
Ловушка, которую легко пропустить: вы добавили Access-Control-Allow-Origin для GET /api/me, но ваш auth‑слой блокирует запрос раньше. Ответ 401 не содержит CORS‑заголовков, и браузер сообщает об ошибке CORS вместо «unauthorized». На деле CORS «сломался», потому что заголовки отсутствуют в ответах об ошибке.
Ещё одна частая ловушка — сочетание credentials и wildcard. Если фронтенд использует cookie (или Authorization и вы ставите withCredentials: true), бэкенд должен вернуть конкретный origin (например https://app.example.com) и разрешить credentials. * не пройдёт.
В AI‑сгенерированном коде такие настройки часто разбросаны между middleware, serverless‑функциями и прокси. FixMyMess регулярно видит проекты, где CORS «настроен» в одном месте, но перезаписывается где‑то ещё. Быстрый путь — проверить реальные ответы в продакшне и сопоставить OPTIONS и финальный ответ, включая ситуации с ошибками.
Пример: фронтенд работает локально, ломается на живом домене
Типичная история: вы сделали React‑приложение на http://localhost:3000 и API на http://localhost:8080. Вы входите, API ставит cookie, и всё работает.
Затем вы деплоите. React уходит на https://app.yourdomain.com, API — на https://api.yourdomain.com, и внезапно появляются CORS‑ошибки после развертывания. Суть в том, что код не менялся.
Что изменилось — это правила браузера. Кросс‑сайт cookie и preflight‑проверки строже на реальных HTTPS‑origin. Локально вы могли не триггерить preflight или dev‑сервер прозрачно проксировал запросы, так что браузер не видел кросс‑origin вызов.
Вот что обычно решает эту конкретную ситуацию:
- Установите
Access-Control-Allow-Originв точное значение фронтенд‑origin (https://app.yourdomain.com), а не*. - Установите
Access-Control-Allow-Credentials: trueи для preflight (OPTIONS), и для реального ответа. - Убедитесь, что сервер отвечает на
OPTIONSдля того же пути 200/204 с теми же CORS‑заголовками. - На фронтенде отправляйте cookie явно (для fetch:
credentials: "include"; для Axios:withCredentials: true). - Убедитесь, что cookie совместимы с кросс‑сайт запросами (часто
SameSite=None; Secure).
Как подтвердить исправление в продакшне: откройте DevTools Network, найдите неудачный запрос и проверьте два элемента. Сначала OPTIONS preflight должен вернуть успех и включать allow‑origin и allow‑credentials. Затем реальный API‑вызов должен вернуть те же заголовки и фактически установить или отправить cookie.
Если AI‑сгенерированный бэкенд непоследователен (OPTIONS обрабатывается одним слоем, реальный запрос — другим), платформы типа FixMyMess быстро находят несоответствие при аудите кода и безопасно поправляют его.
Быстрый чек‑лист и дальнейшие шаги
Когда видите CORS‑ошибки после развертывания, думайте о проблеме как о несовпадении заголовков, а не о тайне. Начните с проверки того, что браузер реально получил в ответе на неудачный запрос и (если есть) на preflight.
Вот быстрые проверки, которые ловят большинство реальных проблем:
- Подтвердите, что Origin совпадает точно с тем, что сервер разрешает (схема + домен + порт).
https://app.comиhttps://www.app.com— разные origin. - Если вы отправляете cookie или auth, убедитесь, что правила для credentials верны: ответ должен содержать
Access-Control-Allow-Credentials: true, и origin не может быть*. - Откройте панель сети и проверьте OPTIONS preflight: он должен вернуть успешный статус (обычно 200 или 204) с теми же CORS‑заголовками, что и реальный запрос.
- Проверьте, что
Access-Control-Allow-Headersвключает то, что вы реально отправляете (частые пропуски:Authorization,Content-Type,X-Requested-With). - Убедитесь, что preflight не перенаправляется (301/302). Редиректы часто происходят после деплоя из‑за HTTP→HTTPS или отсутствия завершающего слэша.
Если всё выше выглядит верно, но проблема остаётся, скорее всего виноват один уровень выше кода: прокси, CDN или настройки платформы переписывают заголовки, кешируют старый ответ или отвечают на OPTIONS иначе, чем ваше приложение.
Дальнейшие шаги, чтобы предотвратить повторение:
- Зафиксируйте разрешённые origin для каждой среды (local, staging, production) и храните их в конфиге, а не разбросанными по файлам.
- Добавьте простой интеграционный чек, который обращается к развернутому домену и проверяет заголовки preflight.
- Выберите один подход к аутентификации (cookie или токены) и приведите CORS и сессионные настройки в соответствие.
- Логируйте OPTIONS‑запросы в продакшне хотя бы временно, чтобы видеть статусы и заголовки.
Если ваше приложение сгенерировано инструментами типа Lovable, Bolt, v0, Cursor или Replit и CORS‑настройки запутаны между кодом и деплой‑настройками, FixMyMess может провести бесплатный аудит кода и быстро привести заголовки бэкенда и конфигурацию платформы в согласие.