Как формируется ответ сервера
Что проверяют
Представляете ли вы путь запроса за пределами fetch: что происходит на сервере, из чего состоит HTTP-ответ и какие его части браузер обрабатывает сам. Уточнение обычно про кеширование или про то, почему запрос упал с ошибкой CORS, хотя сервер вернул 200.
Как отвечать
Коротко пройдите цепочку. Запрос проходит DNS, TCP и TLS, попадает на CDN или обратный прокси вроде nginx, затем в приложение. Там роутер выбирает обработчик, middleware проверяет авторизацию и парсит тело, обработчик ходит в базу или соседние сервисы и сериализует результат. Ответ состоит из трёх частей:
- статусная строка: код из групп 2xx, 3xx, 4xx или 5xx. 201 после создания, 204 без тела, 304 при валидном кеше, 401 значит не авторизован, 403 значит нет прав;
- заголовки:
Content-Type,Cache-Control,ETag,Set-Cookie,Content-Encoding(gzip, br), CORS-заголовки вродеAccess-Control-Allow-Origin; - тело: JSON, HTML, бинарные данные. Может уходить потоком: chunked в HTTP/1.1, фреймами в HTTP/2 и HTTP/3.
Отдельно скажите про фронтенд: fetch не бросает исключение на 404 и 500, успех проверяется по response.ok. Тело читается один раз, методом json() или text(). Для SSR добавьте, что сервер может собрать HTML с данными заранее и отдать его сразу, а клиент затем гидрирует страницу.
Где спотыкаются
Путают статус HTTP с полем status внутри JSON. Если сервер отвечает 200 с {"error": ...} в теле, кеши, мониторинг и response.ok считают это успехом.
Не знают, что CORS проверяет браузер, а не сервер. Простой запрос сервер выполнил и ответил, но без нужного заголовка браузер не отдаёт ответ скрипту. А для запроса с preflight основной запрос вообще не уйдёт.
Забывают про заголовки кеширования. Без Cache-Control браузер и CDN могут закешировать ответ эвристически, и пользователь видит устаревшие данные, или наоборот, запрос уходит на сервер при каждом клике.