После деплоя заказчик пишет: "у меня всё по-старому", хотя на сервере уже лежит новый CSS. Дело не в сломанном сайте, а в браузере: он честно хранит файл стилей в кэше и не полезет качать его заново, пока адрес файла не изменится. Разбираем, как устроен cache-bust ассетов и как перестать просить людей нажимать Ctrl+F5.
Такие сообщения приходят почти после каждого заметного релиза: мелкую правку никто не замечает, а переверстанный блок или новая цветовая схема сразу видны в разнице между тем, что видит разработчик, и тем, что видит клиент у себя. Причина всегда одна: браузер кэширует файл стилей дольше, чем сейчас нужно.
Как браузер решает, скачать файл заново или взять из кэша
Когда браузер впервые получает файл style.css, сервер вместе с ним присылает заголовок Cache-Control со сроком хранения - на практике часто год. Пока адрес файла не поменялся, браузер повторно скачивать его не станет, а возьмёт копию с диска пользователя. Для скорости загрузки это правильно: лишний запрос за файлом, который не менялся неделями, - пустая трата времени, мы писали об этом в статье как ускорить сайт.
Проблема начинается там, где файл поменялся, а адрес - нет. Браузер не заглядывает внутрь файла, прежде чем решить, качать его заново или нет: он смотрит только на адрес и на срок хранения из заголовка. Пока срок не истёк, он честно отдаёт версию из кэша, даже если на сервере давно лежит другая. Отсюда и письма "у меня по-старому".
Три способа сбросить кэш файла
Раз браузер ориентируется на адрес, менять нужно его. Три рабочих варианта.
Параметр версии в конце адреса. style.css?v=33 - то же имя файла с добавкой в конце. Для браузера это уже другой адрес, и старая запись в кэше на него не срабатывает. Поднять цифру можно в одном месте настроек, и весь сайт разом получит новую отметку.
Хеш содержимого в имени файла. Сборщик считает контрольную сумму от содержимого и вписывает её в имя: style.a3f9c1.css. Стоит поменяться хоть одному байту - хеш и адрес меняются автоматически, без участия человека.
Смена всего пути к файлу. Папка с ассетами получает номер релиза: /assets/v34/style.css. Работает как версия в параметре, только версионируется вся папка, а не отдельный файл.
| Способ | Надёжность | Трудоёмкость | Где подводит |
|---|---|---|---|
| Параметр версии в URL | средняя, зависит от человека | низкая, одна настройка | забыли поднять номер после правки |
| Хеш в имени файла | высокая, меняется сам | нужна сборка, которая это считает | сложно подключить без сборщика |
| Смена пути, папка релиза | высокая при аккуратной автоматизации | средняя, нужен процесс выкладки | старые папки копятся, надо чистить |
Почему параметр версии выигрывает по простоте и где он подводит
У большинства сайтов, с которыми мы работаем, нет сборки со сложным пайплайном: HTML, CSS и JS лежат обычными файлами на хостинге. Заводить хеширование ради одной этой задачи означает подключать сборщик там, где его не было, и потом его обслуживать. Параметр версии решает ту же задачу без него: номер - это одна настройка в шаблоне сайта, она подставляется автоматически ко всем ссылкам на стили и скрипты.
Слабое место только одно, зато оно бьёт по всем посетителям сразу. Забыли после правки CSS поднять номер версии - адрес файла не изменился, и кэш браузера не сбросился ни у кого. Со стороны это выглядит так, будто деплой не сработал, хотя файл на сервере уже новый - просто ссылка на него осталась прежней.
Почему заголовком без версии эту задачу не решить
Услышав про проблему с кэшем, легко решить, что дело в заголовке Cache-Control, и урезать срок хранения до пяти минут или до нуля. Так действительно можно избавиться от старого CSS, но ценой становится скорость: браузер начнёт заново скачивать один и тот же файл при каждом переходе по сайту, хотя он не менялся неделями. При заметной посещаемости это лишняя нагрузка на сервер и лишние секунды загрузки у каждого посетителя.
Смена версии решает ту же задачу без этой цены: срок хранения остаётся долгим, а адрес файла меняется только тогда, когда изменилось содержимое.
Ручные версии на отдельных страницах - источник должен быть один
Проблема, с которой мы столкнулись на практике, была не в механизме, а в том, что применяли его не везде одинаково. На нескольких страницах номер версии оказался прописан прямо в разметке, а не подставлен из общей настройки шаблона. После очередной правки CSS общий номер подняли, а эти страницы остались со старым значением, вписанным вручную, - и получили ту же проблему со старыми стилями, только у части посетителей, а не у всех.
Решение не техническое, а организационное: источник номера версии должен быть один на весь сайт, и любая страница обязана брать его оттуда, а не хранить свою копию. Если движок это позволяет, версию стоит вынести в общую настройку - файл конфигурации, переменную окружения, поле в базе - и ссылаться на неё везде одинаково. Та же внимательность нужна после обновления самой CMS: в статье про обновление CMS Битрикс и WordPress разбирали, почему после апдейта тоже стоит проверять версии подключаемых файлов темы.
Промежуточные кэши: страница на сервере и кэш провайдера
Смена версии ассетов решает проблему с кэшем в браузере посетителя, но между сервером и браузером часто стоит ещё один-два уровня кэша, и о них забывают чаще всего.
Кэш страниц на сервере. У многих сайтов кроме файлов стилей кэшируется и готовая HTML-страница целиком - это ускоряет отдачу и снимает нагрузку с движка. Проблема в том, что в закэшированной странице уже записана прежняя ссылка на CSS со старым номером версии. Поднять номер в настройках мало: если страница отдаётся из кэша, в её разметке остаётся прежний адрес файла. Помогает только сброс кэша страниц отдельным действием после выкладки.
Кэш хостинг-провайдера или CDN. Если перед сайтом стоит CDN или прокси-кэш провайдера, копия страницы или файла может лежать и там, независимо от настроек сервера. Когда такой слой вообще нужен, разбирали в статье CDN для сайта. Правило одно: после смены версии ассетов сбрасывать нужно оба уровня кэша, иначе один из них подставит старую версию поверх новой.
Кэш картинок при замене файла под тем же именем
Тот же принцип действует и для картинок. Заменили файл logo.png на новый макет, но оставили прежнее имя и путь - браузер и промежуточные кэши продолжат отдавать старую версию по той же причине: адрес не поменялся, значит, повода качать заново нет.
Для изображений, которые подключены не через общий механизм версии ассетов - логотип в шапке, картинка на посадочной странице, файл в письме, - действует то же правило вручную: либо дать новому файлу другое имя, либо добавить к пути параметр версии. Самая частая ошибка - перезалить файл под тем же именем и решить, что дело сделано.
Порядок выкладки: как проверить, что новую версию видят все
Чтобы не гадать, доехала правка или нет, порядок выкладки удобно один раз зафиксировать и следовать ему при каждом релизе.
- Залить обновлённые файлы CSS и JS на сервер.
- Поднять номер версии в общей настройке - и только там, без ручных правок на отдельных страницах.
- Сбросить кэш страниц на сервере, если он используется, и при необходимости кэш провайдера или CDN.
- Открыть сайт в режиме инкогнито и проверить в коде страницы, что у файла стоит новый номер версии.
- Визуально проверить хотя бы одну страницу с изменённым блоком, а не только главную.
Пятый пункт стоит по опыту: главная страница уже могла закэшироваться у самого разработчика по-другому, а страница, где правка видна, покажет реальную картину. Если сайту нужен постоянный присмотр за такими вещами - версия ассетов, обновления, проверки после правок, - обычно это часть работы техподдержки: что туда входит и сколько это стоит, разбирали в статье техподдержка сайта.
Одна настройка вместо просьбы нажать Ctrl+F5
Кэш браузера - это не баг и не то, что нужно обходить хитрыми заголовками. Он работает ровно так, как задуман: хранит файл, пока не увидит новый адрес. Вся работа с cache-bust ассетов сводится к тому, чтобы адрес менялся ровно тогда, когда меняется содержимое, и делал это из одного места, а не из десятка ручных правок по сайту. Если правки CSS сейчас доезжают до части посетителей через раз, разумнее один раз навести порядок в этой настройке, чем перед каждым релизом гадать, кто и что увидит.
Если планируете новый сайт и не хотите потом разбираться, откуда взялась эта проблема, посчитайте примерный бюджет проекта в калькуляторе - в базовую техническую часть сразу заложена версия ассетов из одной настройки, без ручных вставок номера на отдельных страницах.

