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