Кэширование в браузере: Cache-Control, ETag и сброс кэша
Практика3 минРедакция Innovialabs
Коротко
Статику с хэшем в имени файла кэшируйте надолго с immutable, а HTML отдавайте с no-cache и ETag — тогда пользователи получают обновления сразу, а повторные загрузки почти мгновенны.
Короткий ответ
Браузер решает, брать файл из кэша или идти на сервер, по заголовкам ответа. Рабочая схема для большинства сайтов:
- Статика с хэшем в имени (
app.3f9a2c.js,logo.81bd.svg) —Cache-Control: public, max-age=31536000, immutable. Файл с таким именем никогда не меняется, поэтому его можно хранить год. - HTML и API-ответы —
Cache-Control: no-cacheплюсETagилиLast-Modified. Браузер каждый раз спрашивает сервер «изменилось ли?», и если нет — получает короткий ответ304 Not Modifiedбез тела. - Личные данные —
Cache-Control: private, no-store.
Главные заголовки
| Заголовок | Что делает |
|---|---|
max-age=N | Ответ свежий N секунд, сервер не спрашивается |
no-cache | Хранить можно, но перед использованием нужна проверка на сервере |
no-store | Не сохранять вообще |
private / public | Только браузер / также CDN и прокси |
immutable | Файл не изменится, не нужно перепроверять даже при обновлении страницы |
s-maxage=N | Отдельный срок для CDN и общих кэшей |
ETag | «Отпечаток» версии ресурса |
Last-Modified | Дата последнего изменения |
Частая путаница: no-cache не значит «не кэшировать». Это значит «кэшировать, но всегда перепроверять». Запрет хранения — это no-store.
Как работает ревалидация
- Сервер отдает файл с
ETag: "abc123". - Срок свежести истек (или стоит
no-cache), браузер отправляетIf-None-Match: "abc123". - Если версия та же, сервер отвечает
304без тела — экономится трафик, но не сам запрос. - Если версия новая — приходит
200с новым содержимым и новымETag.
Ревалидация дешевле полной загрузки, но это все равно сетевой запрос. Поэтому для статики лучше вообще избегать запросов через долгий max-age.
Сброс кэша через хэш в имени
Если отдать styles.css с max-age на год, пользователи не увидят новую версию. Решение — cache busting: сборщик (Vite, webpack, Next.js) добавляет в имя файла хэш содержимого. Изменился код — изменился хэш — изменилось имя — браузер скачивает новый файл.
Важное условие: HTML, который ссылается на эти файлы, нельзя кэшировать надолго. Иначе старый HTML будет указывать на старые ассеты. Пример настройки nginx:
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location / {
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.html;
}
Вариант с ?v=2 в адресе тоже работает, но его легко забыть обновить вручную, а хэш сборщик проставляет сам.
Как проверить кэш в DevTools
- Откройте вкладку Network, выберите запрос и посмотрите Response Headers: какой
Cache-Control, есть лиETag. - Колонка Size покажет
(memory cache)или(disk cache)— файл взят из кэша без сети. Статус304— была ревалидация. - Галочка Disable cache отключает кэш, только пока DevTools открыты. Для проверки реального поведения ее нужно снять.
- Обычная перезагрузка может перепроверять ресурсы, поэтому тестируйте также переход по ссылке внутри сайта.
- Учитывайте CDN: заголовки вроде
AgeилиX-Cache(зависит от провайдера) показывают, ответил ли промежуточный кэш.
Частые ошибки
- HTML с долгим max-age. Пользователи днями видят старую версию сайта.
- Долгий кэш у файлов без хэша. Обновили
logo.png— у части посетителей старый логотип. - Ответы с персональными данными без
private. CDN может отдать их другому пользователю. - Разные ETag на разных серверах за балансировщиком — браузер постоянно скачивает файл заново.
- Надежда на
Expires. Если указанmax-age, он важнее; достаточно использоватьCache-Control.