HTTP-кэширование: заголовки, которые имеют значение

Веб · 2 сентября 2026 · ~5 минут

Кэширование кажется простым, пока не начинаешь объяснять, почему no-cache разрешает кэширование. Разбираем по шагам — от заголовка до того, что реально происходит между браузером и сервером.

Cache-Control: три полезных значения

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.

Валидация: ETag и Last-Modified

Вместо того чтобы качать файл целиком, клиент может спросить «а не изменилось ли?»:

< 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% случаев без тонкой настройки.