Сеть, CORS и кэш
Вопрос
Почему fetch на другой домен падает в консоли, хотя сервер ответил 200?
Короткий ответ
Браузер сам ходит на другой источник. CORS решает, можно ли странице прочитать ответ скриптом. Запрос уходит, сервер может ответить 200, а JS получит ошибку, если в ответе нет Access-Control-Allow-Origin, разрешающего этот origin.
Источник — схема, хост и порт. https://api.shop.com и https://shop.com — разные источники.
Простой запрос браузер шлёт сразу: GET, HEAD, POST с простыми заголовками и типом вроде text/plain или application/x-www-form-urlencoded. Запрос с Content-Type: application/json, своим заголовком или методом PUT сначала получает предзапрос OPTIONS. Сервер должен разрешить метод и заголовки. Предзапрос делает браузер, не ваш fetch.
Кэш HTTP: Cache-Control на ответе. no-store не сохраняет. max-age задаёт свежесть. ETag и Last-Modified позволяют условный запрос: 304, тело не качают заново. Это кэш браузера, не Pinia и не память вкладки.
Разбор
Cookie на кросс-доменный запрос уходит только с credentials: 'include' и ответом Access-Control-Allow-Credentials: true. При credentials заголовок Allow-Origin не может быть *, нужен конкретный origin.
Ошибка CORS не лечится на клиенте заголовком запроса. Заголовок ответа выставляет сервер. Прокси в dev-сервере Vite обходит это, потому что для браузера запрос на тот же origin, а сервер разработки ходит к API сам.
fetch не бросает на статусы 4xx и 5xx. Исключение — сеть, CORS, обрыв. Статус проверяют через response.ok или response.status.
На что смотрят на senior
Предзапрос кэшируется через Access-Control-Max-Age. Без этого каждый JSON POST платит лишний OPTIONS.
Приватный ответ с Authorization не должен получить случайный Cache-Control: public на CDN. Иначе один закэшированный JSON может уехать другому пользователю. Для таких ответов — private или no-store.
Service Worker — отдельный слой кэша поверх HTTP. На middle достаточно знать, что он перехватывает запросы и может отдать ответ офлайн. Стратегии cache-first и network-first выбирают по тому, что страшнее: устаревший бандл или пустой экран.
Типичные ошибки
- Искать CORS в Vue и добавлять заголовок на
fetch. - Считать, что 200 в Network значит, что
fetchуспешно прочитан скриптом. - Забывать
response.okи парсить JSON ошибки как успешные данные. - Кэшировать на CDN ответ, который зависит от cookie сессии.
Задача
В Network у fetch('https://api.example.com/me') статус 200, в коде await fetch бросает исключение. Страница открыта с http://localhost:3000. Что проверить в ответе?
Показать ответ
Это разные источники: схема, хост и порт не совпадают. Ответ мог дойти, но браузер не отдаёт тело скрипту без Access-Control-Allow-Origin, который разрешает http://localhost:3000.
Смотреть заголовок ответа, не заголовок запроса. Если запрос с Content-Type: application/json, перед ним ещё будет OPTIONS. fetch при этом не бросает на HTTP 404: исключение здесь именно про CORS или сеть, не про статус 200.