NetrunНа главную

Если что-то не работает

Ошибка CORS после публикации: фронтенд не видит бэкенд

· 6 мин чтения

Коротко

Ошибка CORS означает, что браузер заблокировал ответ бэкенда, потому что сервер не разрешил запросы с адреса вашего фронтенда. Чинится это на бэкенде: в его настройках CORS нужно перечислить точный адрес фронтенда с https и без косой черты в конце. Локально ошибки не было потому, что dev-сервер Vite или Create React App проксировал запросы, и браузер считал фронтенд и бэкенд одним сайтом. Заодно проверьте, что адрес API в коде фронтенда указан с https и задан до сборки, а не после.

  • CORS — правило браузера, поэтому тот же запрос из curl, Postman или с сервера проходит без ошибки.
  • Адреса считаются разными, если отличается хотя бы протокол, домен или порт: http://localhost:5173 и https://app.example.com — это два разных источника.
  • Разрешение «*» в заголовке Access-Control-Allow-Origin браузер не принимает, если запрос идёт с cookies или авторизацией через credentials.
  • Переменные VITE_* и NEXT_PUBLIC_* вшиваются в код фронтенда при сборке, поэтому после их смены проект нужно собрать и опубликовать заново.
  • Страница, открытая по https, не может обращаться к API по http: браузер блокирует такой запрос как смешанное содержимое.

Локально всё работало: фронтенд открывался, данные с бэкенда приходили. После публикации страница пустая, кнопки ничего не делают, а в консоли браузера красная строка вроде «has been blocked by CORS policy: No Access-Control-Allow-Origin header is present». При этом если открыть адрес API прямо в браузере или дёрнуть его через curl, ответ приходит нормальный. Это не поломка хостинга и не ошибка в логике кода — так браузер защищает пользователя.

CORS (дословно «обмен ресурсами между разными источниками») — правило, по которому браузер отдаёт странице ответ от чужого сайта только с разрешения этого сайта. Ваш фронтенд и бэкенд после публикации живут по разным адресам, значит, для браузера это два разных сайта, и бэкенд должен явно сказать: «запросы с такого-то адреса можно». Ниже — почему локально этого не требовалось и как настроить всё правильно.

Как разрешить адрес фронтенда в популярных бэкендах
ФреймворкЧто подключитьЧто указать
FastAPIвстроенный CORSMiddlewareallow_origins со списком адресов фронтенда; allow_credentials=True, если нужны cookies
Flaskпакет Flask-CORSCORS(app, origins=[адрес фронтенда]); supports_credentials=True для cookies
Djangoпакет django-cors-headersCORS_ALLOWED_ORIGINS со списком адресов; CorsMiddleware в MIDDLEWARE как можно выше
Expressпакет corsapp.use(cors({ origin: адрес фронтенда, credentials: true })) до объявления маршрутов
NestJSвстроенный app.enableCorsorigin с адресом фронтенда и credentials: true для cookies
Spring Bootаннотация @CrossOrigin или addCorsMappingsallowedOrigins с адресом фронтенда; allowCredentials(true) для cookies
  1. Поймите, почему локально ошибки не было#

    Во время разработки Vite и Create React App обычно проксируют запросы: в настройке server.proxy у Vite или в поле proxy в package.json у CRA указано, что запросы к /api нужно пересылать на бэкенд. Браузер при этом видит один адрес, localhost:5173, и CORS не включается вовсе. После сборки прокси исчезает: остаются готовые файлы фронтенда, которые обращаются к бэкенду напрямую по другому адресу. Поэтому ошибка появляется именно после публикации, хотя код не менялся.

  2. Разрешите адрес фронтенда на бэкенде#

    Подключите в бэкенде поддержку CORS и перечислите в ней точный адрес, где открывается фронтенд: с https, с доменом, без косой черты и пути в конце. Для FastAPI это встроенный CORSMiddleware, для Flask пакет Flask-CORS, для Django пакет django-cors-headers, для Express пакет cors — точные настройки собраны в таблице. Если у вас и локальный, и опубликованный фронтенд, перечислите оба адреса. Удобно хранить список в переменной окружения, чтобы менять его без правки кода.

  3. Не ставьте звёздочку, если используете cookies#

    Разрешение «*» значит «любой сайт», и для открытого API без входа это допустимо. Но если фронтенд отправляет cookies или запрос с credentials, браузер отвергнет ответ со звёздочкой, и ошибка CORS останется, только с другим текстом. В этом случае укажите конкретный адрес фронтенда и включите разрешение на credentials в настройках CORS. Это ещё и безопаснее: чужая страница не сможет делать запросы от имени ваших пользователей.

  4. Задайте адрес API до сборки фронтенда#

    Фронтенд узнаёт адрес бэкенда из переменной: у Vite она должна начинаться с VITE_, у Next.js с NEXT_PUBLIC_, у Create React App с REACT_APP_. Такие переменные подставляются в код в момент сборки, а не при открытии страницы, поэтому смена значения после сборки ничего не даст. Надёжный способ — положить в папку фронтенда файл .env.production со строкой вроде VITE_API_URL и адресом бэкенда: этот адрес не секрет, он всё равно виден любому посетителю в коде страницы. После правки опубликуйте фронтенд заново.

  5. Проверьте, что API вызывается по https#

    Если страница открыта по https, а адрес API в коде начинается с http, браузер заблокирует запрос как смешанное содержимое, и это легко принять за ту же ошибку CORS. Перепишите адрес на https. Отдельно проверьте, не остался ли в коде http://localhost:8000 или похожий локальный адрес: опубликованная страница будет стучаться на компьютер посетителя, а не на ваш сервер.

  6. Убедитесь, что бэкенд отвечает на предварительный запрос#

    Перед запросом с JSON или заголовком Authorization браузер сначала отправляет короткий предварительный запрос методом OPTIONS и ждёт в ответ заголовки CORS. Подключённая библиотека CORS отвечает на него сама, но если бэкенд защищает все маршруты проверкой входа, она может отбить и этот запрос, и тогда основной не уйдёт вовсе. В консоли браузера на вкладке «Сеть» видно, на каком из двух запросов всё остановилось. Если упал OPTIONS, пропустите его мимо проверки авторизации.

Ошибка CORS почти всегда закрывается тремя действиями: точный адрес фронтенда в настройках бэкенда, адрес API с https в переменной сборки и повторная публикация фронтенда. Есть и способ обойтись без CORS вовсе: отдавать собранный фронтенд тем же приложением, что и API, тогда адрес у них один. В Netrun каждый проект получает свою ссылку с HTTPS, поэтому фронтенд и бэкенд, опубликованные отдельными проектами, — это два адреса, и настройка CORS нужна; логи бэкенда при этом видны в кабинете в реальном времени. Учтите, что на бесплатном тарифе доступен один проект. Попробовать Netrun.

Частые вопросы

Почему через Postman запрос работает, а из браузера нет?

Потому что CORS проверяет только браузер. Postman, curl и серверный код не смотрят на заголовки Access-Control-Allow-Origin и показывают ответ как есть. Браузер же защищает пользователя от чужих страниц и без разрешения бэкенда не отдаёт ответ скрипту на странице.

Можно ли починить CORS на стороне фронтенда?

Нет, разрешение выдаёт только сервер, к которому идёт запрос. Режим no-cors в fetch ошибку не убирает, а просто прячет содержимое ответа, поэтому данные всё равно не прочитать. Нужно либо настроить CORS на бэкенде, либо отдавать фронтенд и API с одного адреса.

Что делать, если бэкенд чужой и настроить его нельзя?

Тогда запросы к нему нужно делать не из браузера, а со своего сервера: ваш бэкенд обращается к чужому API и пересылает ответ фронтенду. Серверные запросы правилу CORS не подчиняются. Заодно ключи доступа к чужому API останутся на сервере и не попадут в код страницы.

Безопасно ли поставить allow_origins равным звёздочке?

Для открытого API без входа и без cookies это допустимо: любой сайт сможет читать ответы, но они и так публичные. Если есть вход, cookies или личные данные, указывайте конкретные адреса фронтенда. Со звёздочкой и credentials браузер всё равно откажет.

Почему ошибка CORS появляется только иногда?

Частая причина — сам бэкенд падает с ошибкой 500 или 502, а такой ответ приходит без заголовков CORS, и браузер пишет про CORS, хотя дело в другом. Посмотрите логи бэкенда в момент ошибки. Если там исключение, чинить нужно его, а настройки CORS в порядке.

А React, Vue, Next.js, Nuxt, Svelte или Astro?

Да, все они работают. Загрузите исходный код с package.json — собирать проект у себя и класть готовую сборку в архив не нужно, сборка идёт у нас. Если фреймворк умеет и в готовые страницы, и в серверный режим, подойдут оба варианта: в серверном приложение должно слушать порт из переменной окружения. Одна важная деталь: значения, которые попадают в собранный фронтенд, видны любому посетителю — настоящие ключи держите на стороне сервера.

Где указать токены и другие секретные значения?

В проекте есть вкладка «Секреты» — там задаются значения из кода, например токен от BotFather. Мы храним их в зашифрованном виде: названия переменных вы видите, сами значения не показываем никому, включая вас.

Где посмотреть логи и статус проекта?

На странице проекта есть статус, логи и история событий — по ним видно, что происходит с проектом прямо сейчас. Лента логов показывает и более ранние строки: пролистайте её вверх, и они подгрузятся сами.

Запустите свой проект

Загрузите код, ответьте на пару вопросов — и получите рабочую ссылку. Есть бесплатный тариф

Попробовать Netrun
Все статьи блога