7 мин. чтения

Ошибки CORS после развертывания: реальные причины и способы исправления

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

Ошибки CORS после развертывания: реальные причины и способы исправления

Почему 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.com vs https://www.example.com
  • Отсутствие порта: https://example.com vs https://example.com:8443
  • Стейджинг-домены: https://staging.example.com vs https://app.example.com
  • Неожиданные локальные origin: http://127.0.0.1:3000 vs http://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-Origin
  • Access-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: самая частая продакшн‑ловушка

Ship a safer API
We harden AI-generated APIs and fix common risks like injection and weak auth flows.

Многие ошибки CORS после развертывания связаны с правилом, которое узнают только в продакшне: нельзя сочетать credentials и wildcard‑origin.

Если браузер отправляет cookie (или вы указываете ему отправлять credentials), сервер должен возвращать конкретный origin, а не *. Поэтому вот так не сработает:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Credentials: true

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: true
  • Vary: 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 (шаг за шагом)

Audit your deployed headers
We compare CDN, proxy, and app responses to find what is rewriting your CORS headers.

Когда появляются ошибки CORS после развертывания, относитесь к этому как к задаче отладки, а не к угадыванию. Цель — найти точный запрос, который браузер блокирует, и тот самый заголовок, который отсутствует или отличается.

Начните с вкладки Network в DevTools. Отфильтруйте неудачный API‑запрос и посмотрите, есть ли перед ним OPTIONS. Если видите OPTIONS, вы имеете дело с preflight. Если нет — ошибка обычно связана с реальным запросом (часто отсутствует заголовок в ответе 401/500).

Используйте эту последовательность и не пропускайте шаги:

  1. Воспроизведите и зафиксируйте неудачный запрос: скопируйте детали из Network (метод, URL, код статуса, и был ли preflight).
  2. Подтвердите точный Origin: прочитайте заголовок Origin и запишите его точно (схема + домен + порт). Многие «совпадения» на самом деле связаны с http vs https или www vs без www.
  3. Проверьте заголовки по обеим ответам: посмотрите ответ OPTIONS и ответ реального запроса. Оба должны содержать нужные CORS‑заголовки (особенно Access-Control-Allow-Origin, и для cookie: Access-Control-Allow-Credentials).
  4. Устраните редиректы и неожиданные middleware: если API делает редирект (301/302) или принуждает слэш, preflight часто падает, потому что редирект‑ответ не содержит CORS. Сделайте так, чтобы OPTIONS возвращал 200/204 прямо с заголовками.
  5. Переключитесь с * на явный 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 и финальный ответ, включая ситуации с ошибками.

Пример: фронтенд работает локально, ломается на живом домене

Get production-ready quickly
Most projects complete in 48-72 hours after a free audit and clear fix plan.

Типичная история: вы сделали 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 может провести бесплатный аудит кода и быстро привести заголовки бэкенда и конфигурацию платформы в согласие.

Ошибки CORS после развертывания: реальные причины и способы исправления | fixmymess.ai