Кэширование кажется простым, пока не начинаешь объяснять, почему no-cache разрешает кэширование. Разбираем по шагам — от заголовка до того, что реально происходит между браузером и сервером.
Cache-Control: max-age=3600, public
Cache-Control: no-store
Cache-Control: no-cache, must-revalidate
| Директива | Поведение |
|---|---|
max-age=3600 | Можно хранить и использовать без проверки 3600 секунд |
no-store | Не сохранять вообще — приватные данные, токены |
no-cache | Хранить можно, но перед использованием обязательно спросить сервер |
must-revalidate | После истечения срока — не использовать просроченное, только перезапросить |
private | Только браузеру, не общим кэшам и CDN |
no-cache ≠ «не кэшируй». Это «кэшируй, но всегда проверяй». Настоящее «не кэшируй» — no-store.
Вместо того чтобы качать файл целиком, клиент может спросить «а не изменилось ли?»:
< HTTP/1.1 200 OK
< ETag: "a1b2c3d4"
< Last-Modified: Wed, 02 Sep 2026 10:00:00 GMT
> GET /data.json
> If-None-Match: "a1b2c3d4"
< HTTP/1.1 304 Not Modified
Ответ 304 не содержит тела — только подтверждение, что кэш годен. Это экономит трафик, но не время одного сетевого круга; max-age экономит и его тоже.
То, что я ставлю по умолчанию для статических ассетов:
# с версией или хешем в имени файла
/assets/app.a1b2c3d4.js
Cache-Control: public, max-age=31536000, immutable
# HTML-документы
/
Cache-Control: no-cache
Ключ в том, что неизменяемые ассеты имеют хеш в имени: их можно кэшировать вечно, а новый деплой просто даёт новое имя файла. HTML — короткий срок, чтобы пользователь получил свежую ссылку.
Цепочка обычно выглядит так: браузер → общий кэш/прокси → CDN → сервер. Заголовок Vary подсказывает, по чему различать варианты:
Vary: Accept-Encoding, Accept-Language
Без корректного Vary CDN может отдать сжатый ответ клиенту, который его не поддерживает, или англоязычную страницу русскому пользователю.
curl -I https://example.com/app.js
curl -I -H 'If-None-Match: "a1b2c3d4"' https://example.com/app.js
# DevTools → Network → колонка «Size»: «memory cache» / «304»
Минимальная рабочая схема: хешированные имена для ассетов +immutableдля них +no-cacheдля HTML. Это закрывает 95% случаев без тонкой настройки.