Можно ли кэшировать POST-запросы с учетом их семантики
Что проверяют
Знаете ли вы, что говорит о POST спецификация HTTP, и отличаете ли HTTP-кэш от кэша данных на клиенте. Следом обычно спрашивают, как тогда закэшировать поиск со сложными фильтрами, который по историческим причинам ходит POST-запросом.
Как отвечать
Начните с семантики. POST не безопасный и не идемпотентный метод: он может создавать и менять данные, поэтому повторять его или подменять ответ из кэша опасно. Дальше разведите уровни:
- спецификация: RFC 9110 формально разрешает кэшировать ответ на POST, если в нём есть явная свежесть (
Cache-Control: max-age) иContent-Location, совпадающий с адресом запроса. Такой ответ можно отдать на последующий GET, но не на повтор POST; - браузеры и CDN: на практике ответы на POST не кэшируют. Опция
cacheвfetchэтого не меняет, аcache.put()в Service Worker с POST-запросом бросаетTypeError; - кэш данных: чтение через POST (GraphQL, поиск с большим телом) кэшируют на уровне приложения. TanStack Query или SWR хранят ответ по ключу из параметров, Apollo Client держит нормализованный кэш;
- перевод на GET: если запрос только читает, лучше сделать его GET. В GraphQL для этого есть persisted queries: клиент шлёт GET с хэшем запроса, и ответ кэшируется на CDN. Для чтения с телом обсуждается метод
QUERY, но это пока черновик стандарта.
Упомяните и обратную сторону: чтобы повторная отправка формы не создала дубль, нужно не кэширование, а идемпотентность на сервере, например ключ идемпотентности в заголовке запроса.
Где спотыкаются
Отвечают «POST кэшировать нельзя по стандарту». Стандарт это допускает, не кэширует практика, и такая точность показывает, что вы читали первоисточник.
Советуют ставить Cache-Control: no-store на каждый POST, чтобы «запретить кэширование». Браузер его и так не кэширует, заголовок ничего не решает.
Кэшируют ответ по телу запроса без нормализации: {"a":1,"b":2} и {"b":2,"a":1} дают разные ключи. И забывают сбрасывать такой кэш после мутаций, из-за чего интерфейс показывает устаревшие данные.