Кэш браузера и сжатие статики - заголовки, gzip, версии файлов
Настраиваем, сколько браузер хранит картинки, стили и скрипты и в каком виде получает их с сервера. Задача всплывает после первого замера скорости, когда статика приезжает заново на каждом переходе.
Решение
Кто отдаёт статику
Смотрим, какие типы файлов уходят мимо PHP:
# правила отдачи статики живут в отдельном подключаемом файле окруженияgrep -n 'location ~\*\|expires\|gzip' /etc/nginx/bx/conf/bitrix_general.conf# при ручной установке тот же файл лежит в /etc/nginx/conf.d/nginx -T | grep -c 'location ~\*' # сколько блоков правил подключено на самом делеls /etc/nginx/bx/conf/*.conf # свои директивы кладут отдельным файлом рядомКартинки, стили, скрипты и шрифты веб-сервер отдаёт с диска сам, не обращаясь к
PHP. Правила из .htaccess до них не доходят: этот файл читает Apache, а запрос
за существующим файлом до Apache не добирается.
Срок хранения
Задаём срок хранения по типам файлов:
<IfModule mod_expires.c> ExpiresActive on ExpiresByType image/jpeg "access plus 3 day" ExpiresByType text/css "access plus 3 day" ExpiresByType application/javascript "access plus 3 day"</IfModule>Именно так это записано в корневом .htaccess поставки: срок хранения статики -
трое суток. До момента устаревания браузер берёт файл из своего кэша и на сервер
за ним вообще не ходит.
Переносим то же правило на фронтенд:
location ~* \.(css|js|jpg|png|svg|woff2)$ { expires 3d; # момент устаревания и срок хранения сразу add_header Cache-Control "public"; # ответ разрешено хранить и посредникам access_log off; # обращения за статикой журнал не разбирает}
location ~* ^/bitrix/cache/(css|js)/ { expires 1y; # у собранных наборов метка версии в адресе}Одна директива печатает оба заголовка: точную дату устаревания и срок хранения в
секундах. Значение off их убирает, и тогда браузер решает сам, сколько держать
файл у себя.
Сжатие ответов
Включаем сжатие текстовых типов:
gzip on;gzip_types text/css application/javascript image/svg+xml application/json;gzip_min_length 1024; # мелкий файл дешевле отдать как естьgzip_vary on; # добавляет Vary: Accept-Encoding к сжатым ответамgzip_proxied any; # иначе ответ от второго веб-сервера уйдёт без сжатияgzip_static on; # рядом лежит готовый .gz - отдаём его, а не сжимаем сноваСжимаются только перечисленные типы. Форматы JPEG и PNG сжаты внутри себя, и второй проход отнимает процессорное время, не давая выигрыша в весе ответа.
Новая версия файла после выкладки
Добавляем метку версии в адрес файла:
// метка версии в адресе: для браузера это другой файл$asset = \Bitrix\Main\Page\Asset::getInstance();$asset->addCss('/local/templates/main/style.css?20260826');$asset->addJs('/local/js/shop/cart.js?20260826');Срок хранения в трое суток означает, что правку часть посетителей увидит через трое суток. Метка версии снимает это ограничение: адрес изменился, и прежний ответ из кэша браузера к новому адресу не подходит.
Проверка запросом
Смотрим, что реально приходит браузеру:
curl -sI -H 'Accept-Encoding: gzip' https://example.ru/local/templates/main/style.css \ | grep -i 'expires\|cache-control\|content-encoding\|vary'# на HTML-странице те же заголовки приходят уже от PHP, а не от фронтендаcurl -so /dev/null -w '%{size_download}\n' https://example.ru/ # без сжатияcurl -so /dev/null -w '%{size_download}\n' --compressed https://example.ru/Заголовок Vary: Accept-Encoding обязателен там, где один адрес отдаётся и
сжатым, и обычным. Без него посредник способен вернуть сжатый ответ клиенту,
который сжатие не запрашивал.
Что делает платформа
Свою часть работы платформа закрывает сама. Объединённые наборы стилей и скриптов получают метку версии, которая меняется вместе с содержимым набора. Композитные страницы отдаются с заголовком последнего изменения, и ответ «не изменилось» приходит с третьего обращения: первый хит создаёт кэш, второй отдаёт метку, третий присылает её обратно.
Срок хранения и сжатие платформа не трогает - это правила веб-сервера. Свои директивы кладут в отдельный подключаемый файл, потому что сгенерированные конфиги окружение переписывает при следующем изменении настроек через меню. Каталог с тысячами картинок разумно вынести на отдельный домен или в сеть доставки: к одному домену браузер держит около шести одновременных загрузок.
Типичные проблемы
Сжатие включено, а JPEG всё равно приходит несжатым.
Формат сжат внутри себя, и второй проход не даёт выигрыша в весе. Список типов для сжатия картинки не включает намеренно, и это правильное поведение.
Правка стилей видна только после жёсткой перезагрузки.
Прежний файл лежит в кэше браузера до конца срока хранения. Адрес не изменился, поэтому повода скачивать файл заново у браузера нет.
Отключил Expires везде, а заголовок с датой 1981 года остался.
Такой заголовок печатает PHP при старте сессии, а не веб-сервер. Правила веб-сервера отменяют только собственные заголовки на статических файлах.
Правило expires off для выгрузок не сработало.
Запрос попал в другой блок правил, совпавший с адресом раньше нужного. Порядок блоков и точность выражения решают, какое правило в итоге применится.
Статика сжимается, а HTML-страница приходит без сжатия.
Страницу считает PHP и возвращает через второй веб-сервер, а сжатие настроено только на фронтенде. Сжатие проксированных ответов включают отдельной настройкой.
Частые вопросы
Можно ли задать правила заголовков где-то помимо .htaccess?
Для статики только так и можно: существующий файл отдаёт фронтенд, и до Apache запрос не доходит. Срок хранения и сжатие для картинок, стилей и скриптов задают в конфиге веб-сервера.
Какой срок хранения ставить для css и js?
В поставке стоят трое суток, и для файлов без метки версии это разумный потолок. С меткой версии в адресе срок поднимают до года: новый адрес всё равно обойдёт кэш браузера.
Зачем сжатие, если картинки от него не выигрывают?
Выигрыш даёт текст: HTML, CSS, JS, SVG и JSON сжимаются в разы. На странице каталога это десятки килобайт экономии при каждом заходе нового посетителя.
Почему сайт медленный, хотя заголовки настроены?
Кэш браузера ускоряет повторные визиты и совсем не помогает первому заходу. Первое открытие упирается в вес вёрстки, число отдельных файлов и время ответа PHP.
Стоит ли выносить статику на отдельный домен?
На большом каталоге стоит: к одному домену браузер держит около шести одновременных загрузок. Отдельный домен или сеть доставки снимают этот предел и разгружают основной сервер.
Смежное
- nginx и PHP-FPM - оглавление подтемы
- Сервер и поиск - устройство сервера целиком
- nginx и PHP-FPM: разделение статики, пулы, медленные ответы - кто и какие запросы обслуживает
- Файл .htaccess на платформе: что работает, что ломает - какие директивы читает Apache
- Объединение и сжатие стилей и скриптов - сборка наборов на стороне платформы
- Быстрые картинки на витрине: размеры, ленивая загрузка, WebP - вес самих файлов, а не заголовки
- Правка не видна на сайте: какой кэш сбросить - кэш платформы, а не браузера
- Композитный сайт: настройка, сброс кэша и диагностика - откуда берётся ответ «не изменилось»
- Сайт тормозит: разбор причин - когда дело не в статике
- Переезд на HTTPS: контент, база, интеграции - смена протокола и адреса файлов