Веб-разработка

HTTP-кэширование простыми словами: Cache-Control, ETag и почему сайт не обновляется

Сайт открывается почти мгновенно при повторном визите — и та же самая система через неделю после деплоя показывает пользователю старую версию главной страницы с багом, который вы уже починили. За оба поведения отвечает один и тот же механизм: HTTP-кэширование. Он работает молча и без участия разработчика ровно до того момента, пока не начинает работать неправильно, и тогда разбираться приходится в спешке. Посмотрим, как браузер на самом деле решает, что взять из кэша, а что перезапросить у сервера.

Что кэшируется и зачем это вообще нужно

Когда браузер получает ответ от сервера — HTML-страницу, CSS-файл, картинку, скрипт — он может сохранить этот ответ локально и в следующий раз отдать его пользователю без обращения к серверу. Выгода очевидна: страница открывается быстрее, сервер получает меньше запросов, а на медленном или дорогом мобильном соединении экономится трафик. Кэшировать может не только браузер: между пользователем и вашим сервером часто стоит ещё CDN или прокси, и у них своя копия того же ответа.

Проблема в том, что кэш — это всегда компромисс между скоростью и актуальностью данных. Если закэшировать слишком агрессивно, пользователи будут видеть устаревший контент. Если не кэшировать вообще, теряется весь выигрыш в скорости и нагрузка на сервер растёт без необходимости. Управляют этим балансом HTTP-заголовки, которые сервер добавляет к каждому ответу.

Cache-Control — главный заголовок, который задаёт правила

Заголовок Cache-Control — это набор директив, которыми сервер объясняет браузеру и промежуточным кэшам, как обращаться с конкретным ответом. Самые важные из них:

Именно путаница вокруг no-cache — одна из самых частых ошибок в этой теме: разработчики ставят его, ожидая, что браузер вообще перестанет кэшировать ответ, а на деле получают ровно то же самое количество запросов проверки свежести, но не полный обход кэша.

ETag и Last-Modified: как сервер и браузер договариваются о свежести

Директива no-cache работает только потому, что существует механизм «условных запросов». Сервер может добавить к ответу заголовок ETag — что-то вроде отпечатка содержимого файла — или Last-Modified с датой последнего изменения. Когда браузеру нужно проверить, не устарела ли кэшированная копия, он не запрашивает файл заново целиком, а отправляет условный запрос с этими данными:

Если содержимое не изменилось, сервер отвечает не полным файлом, а коротким статусом 304 Not Modified — и браузер просто продолжает использовать то, что уже есть в кэше.

Это выглядит примерно так на уровне заголовков:

Такой запрос всё равно доходит до сервера и тратит одно сетевое обращение, но экономит передачу самого содержимого — иногда это мегабайты, а иногда несколько байт, если файл маленький. Поэтому связка max-age для «не спрашивать вообще» и ETag для «спросить дёшево» обычно используется вместе, а не вместо друг друга.

Почему сайт «не обновляется» после деплоя

Самый частый повод для жалоб пользователей — задеплоили исправление, а часть посетителей продолжает видеть старую версию. Обычно причина в том, что у HTML-страницы или у бандла со скриптами стоит слишком долгий max-age, и браузер честно выполняет то, что ему сказали: не ходит на сервер, потому что ответ формально ещё не устарел.

Практика, которая решает эту проблему почти во всех современных сборщиках, — версионирование имён файлов. Файлы вроде app.a1b2c3.js получают долгий max-age и даже immutable, потому что при изменении содержимого у файла в принципе появляется новое имя со своим хэшем — старое имя действительно никогда не поменяется. А сам HTML-документ, который на эти файлы ссылается, наоборот, кэшируют с no-cache или очень коротким сроком жизни, чтобы браузер каждый раз проверял, не появилась ли новая версия разметки со ссылками на новые файлы.

Если в проекте нет версионирования по хэшу, а статические файлы просто лежат под одним и тем же именем годами, любой долгий max-age на них становится риском: чтобы обновить файл для уже закэшировавших его пользователей, придётся либо менять имя файла вручную, либо ждать, пока истечёт срок жизни кэша у каждого конкретного посетителя.

Итог

HTTP-кэширование — это не один переключатель «включено/выключено», а набор из нескольких заголовков, которые вместе решают, когда можно доверять локальной копии, а когда нужно проверить сервер. Cache-Control задаёт общие правила и срок жизни, ETag и Last-Modified позволяют дёшево проверить актуальность без повторной загрузки содержимого, а версионирование имён файлов снимает противоречие между «кэшировать надолго» и «обновлять сразу после деплоя». Если пользователи жалуются на устаревший контент — почти всегда стоит начать разбор именно с заголовков ответа, а не с догадок о том, что «браузер что-то подглючивает».

← Все статьи
Я люблю Алину Цой (Билялову)