05 нояб. 2025 г.·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: самая частая продакшн‑ловушка

Rescue an AI-generated app
If your Lovable, Bolt, v0, Cursor, or Replit app breaks in production, we can fix it.

Многие ошибки 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 (шаг за шагом)

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

Когда появляются ошибки 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 и финальный ответ, включая ситуации с ошибками.

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

Find exposed secrets now
Our audit flags exposed secrets and risky config that often ship with generated code.

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