Когда CORS не сходится, браузер пишет в консоль много букв. Поначалу пугающе. На самом деле сообщения структурированные, по ним всегда видно, в чём проблема — нужно только знать, на какие слова смотреть.

Самая частая ошибка

Access to fetch at 'https://api.example.com/posts' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

Что значит. Сервер вообще не сказал «кому можно». Браузер по умолчанию это понимает как «никому» и блокирует чтение ответа.

Что делать: проблема на сервере. Бэкенду нужно добавить заголовок Access-Control-Allow-Origin с вашим origin (или *, если API публичный).

Второй тип: origin указан, но не наш

Access to fetch at 'https://api.example.com/posts' from origin 'http://localhost:3000'
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value
'https://prod.example.com' that is not equal to the supplied origin.

Что значит. Сервер кому-то разрешил — но не вам. Часто бывает, когда бэк прописывает захардкоженно прод-домен, а вы пытаетесь развиться на localhost.

Что делать: либо бэкенд добавляет ваш origin в whitelist, либо разворачиваете локально через dev-proxy (см. ниже).

Третий тип: запрещённый заголовок

Request header field authorization is not allowed by Access-Control-Allow-Headers
in preflight response.

Что значит. Preflight прошёл, сервер ответил списком разрешённых заголовков — но Authorization в этом списке отсутствует. Браузер отказывается слать настоящий запрос с этим заголовком.

Что делать: бэкенду нужно добавить Authorization в Access-Control-Allow-Headers.

Четвёртый тип: куки и Allow-Credentials

Access to fetch at '...' has been blocked by CORS policy: The value of the
'Access-Control-Allow-Credentials' header in the response is '' which must be
'true' when the request's credentials mode is 'include'.

Что значит. Послали запрос с credentials: 'include', а сервер не подтвердил, что разрешает куки.

Что делать: либо бэкенд добавляет Access-Control-Allow-Credentials: true и точный (не *) origin, либо — если куки реально не нужны — убираете credentials: 'include' со стороны фронта.

Кто что лечит

Проблема Кто чинит Как
Нет Access-Control-Allow-Origin бэкенд добавить заголовок с вашим origin
Allow-Origin не ваш бэкенд whitelist или динамическое отражение Origin
Allow-Headers не покрывает заголовок бэкенд дописать имя заголовка
Allow-Methods не покрывает метод бэкенд дописать метод (часто DELETE/PATCH забывают)
Allow-Credentials = false при include и тот, и тот бэк включает, либо фронт убирает include
Preflight каждый раз — медленно бэкенд добавить Access-Control-Max-Age

Большинство строк в этой таблице — задача бэкенда. Фронт CORS «починить» в большинстве случаев не может: это решение браузера, основанное на ответе сервера.

Что может сделать фронт в dev-окружении

Когда нужно разработать прямо сейчас, а бэкенд CORS обещает добавить «на следующей неделе» — есть несколько обходов на время разработки.

1. Dev-proxy в Vite / Webpack

Самый частый способ. В конфиге dev-сервера прописываете, что запросы по определённому префиксу проксируются на бэк. Для браузера запрос идёт на свой localhost:3000 — CORS не нужен. Dev-сервер уже на своей стороне переадресует запрос на бэк.

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
      },
    },
  },
};

Теперь fetch('/api/posts') на фронте уходит к Vite-серверу, тот переадресует на https://api.example.com/api/posts. CORS не вмешивается, потому что для браузера всё происходит в рамках одного origin.

Это решение только для dev. В проде такого proxy уже не будет, и без настоящего CORS на бэке ничего работать не будет.

2. Отключение CORS в браузере

В Chrome есть флаг --disable-web-security, который выключает same-origin policy и CORS целиком. Запускается отдельным профилем, использовать только для отладки и никогда — в обычном браузере.

Это не решение — код будет работать только у вас. Скорее, способ убедиться, что проблема именно в CORS, а не в чём-то ещё.

3. Сторонние CORS-прокси (например, cors-anywhere)

Сервисы, через которые можно обернуть запрос: ваш JS дёргает прокси, прокси дёргает реальный API. Прокси отвечает с правильными CORS-заголовками.

В продакшен такое не выкатывать — зависимость от чужого сервера, который может упасть, иметь лимиты или потребовать оплаты. Для быстрой проверки гипотезы — нормально.

Чек-лист, когда упёрся в CORS

  1. Открой DevTools → Network. Посмотри, есть ли OPTIONS перед твоим запросом и каков ответ на него.
  2. Прочитай сообщение в Console — в нём явно указано, чего не хватает.
  3. Если виноват сервер — сформулируй для бэкенда конкретно: «нужен Allow-Origin для http://localhost:3000 и в Allow-Headers — Authorization».
  4. На время разработки — подними dev-proxy в Vite или Webpack.
  5. В долгосрочной перспективе — CORS должен быть нормально настроен на бэке. Точка.