Как работают основные директивы Cache-Control
Заголовок Cache-Control состоит из нескольких директив, каждая регулирует разные аспекты кэширования. Самые важные определяют, кто вообще может хранить ресурс и на какой срок.
public и private задают видимость. cache-control: public означает, что ресурс может кешироваться везде — в браузере пользователя, на прокси-серверах, в CDN. Копию сохранит каждый. cache-control: private говорит: только браузер клиента может хранить ресурс локально. Прокси и промежуточные системы не трогают.
На практике: статический файл CSS или JavaScript вроде styles.abc123.css — это public. API-ответ с персональными данными пользователя? Это private. Иначе прокси-сервер отправит кэшированный ответ от одного клиента другому, и тот получит не свои данные.
max-age — время жизни кэша в секундах. На 31536000 (один год) ставят статические файлы с версией в имени файла. На 3600 (час) — HTML-страницы, которые обновляются регулярно. На 0 — данные, которые меняются с каждым запросом.
Распространённая ошибка: путают max-age со сроком хранения. На самом деле это время, после которого браузер перестает верить кэшированной копии и приходит на сервер. Но идет не за полной загрузкой — идет за проверкой.
no-cache — самая запутанная директива Cache-Control. cache-control: no-cache не значит «не кешировать». Это значит «кешировать, но перед использованием всегда проверять на сервере». Браузер загружает ресурс, сохраняет его в памяти, но перед каждым применением отправляет на сервер запрос: «у тебя есть свежая версия?»
no-store — вот это действительно запрещает кэширование. cache-control: no-store приказывает браузеру не сохранять ресурс вообще, даже временно. Используется для чувствительных данных пользователя, токенов, паролей — всего, что не должно оставаться на диске.
Механизм 304 Not Modified и ETag
Когда заголовок Cache-Control содержит no-cache, браузер отправляет на сервер специальные заголовки: If-None-Match (включает ETag) или If-Modified-Since (включает дату). Сервер получает запрос и отвечает:
- Если ETag совпадает — код 304 Not Modified. Браузер использует кэшированную версию.
- Если дата совпадает (через Last-Modified) — опять 304.
- Если ресурс конкретно изменился — полный ответ с кодом 200, новый контент, новый ETag.
Это экономит трафик. Полный ответ может занять мегабайт, а проверка через ETag — килобайты. При повторном запросе браузер не загружает весь файл заново, если на сервере ничего не изменилось.
Конфиги для разных типов контента
Статические файлы (CSS, JS, изображения с версией в имени):
cache-control: public, max-age=31536000, immutable
Директива immutable говорит: файл никогда не изменится, проверять не нужно. С версией в URL это создает идеальную систему — браузер месяцами берет ресурс из памяти.
HTML-страницы:
cache-control: public, max-age=3600, no-cache
HTML хранится в кэше час, но перед использованием браузер проверяет ETag на сервере. Если контент не изменился — 304, браузер использует версию из памяти. Если вышло обновление — загружается новый HTML.
API с персональными данными:
cache-control: private, no-store
Или если данные остаются достаточно свежими несколько минут:
cache-control: private, max-age=300, no-cache
Редко обновляемые изображения:
cache-control: public, max-age=2592000
Это 30 дней. Браузер не проверит обновления месяц.
s-maxage и must-revalidate
Директива s-maxage задает max-age специально для прокси, CDN и других промежуточных кэшей. Запись cache-control: public, max-age=3600, s-maxage=86400 означает: браузер кеширует на час, CDN кеширует на день. Когда трафик идет через CDN на миллионы клиентов, это значительно снижает нагрузку на серверы.
must-revalidate — жесткое требование. После истечения max-age браузер обязательно проверит ресурс на сервере, копии в памяти он больше не доверяет. Без этого браузер при потере интернета может использовать устаревший кэш.
Базовая конфигурация для nginx
Этот конфиг работает для большинства приложений. Добавьте в блок server:
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location ~* \.html$ {
add_header Cache-Control "public, max-age=3600, no-cache";
}
location /api/ {
add_header Cache-Control "private, no-store";
}
Откройте DevTools браузера (F12), вкладка Network, перезагрузите страницу. Смотрите, где ресурсы загружаются из памяти «(from cache)» и где браузер отправляет запросы на сервер. Если CSS грузится из памяти, а HTML каждый раз проверяется на сервере — конфигурация работает правильно.
О версионировании ресурсов
Все это эффективно, только если вы версионируете статические файлы. CSS должен быть вроде styles.abc123.css, где хеш меняется при обновлении кода. Браузер видит новый URL, не находит его в памяти, загружает свежий файл. Старая версия styles.xyz.css остается в браузере год благодаря immutable.
Если всегда выдаете файл с одним именем (style.css), то max-age 31536000 будет ошибкой — браузер не обновится год, пользователи увидят старый дизайн. Тогда нужен max-age 0 или no-cache, зато браузер каждый раз проверяет актуальность через ETag и Last-Modified.
