Перейти к содержимому

Кэш браузера и сжатие статики - заголовки, 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.

Стоит ли выносить статику на отдельный домен?

На большом каталоге стоит: к одному домену браузер держит около шести одновременных загрузок. Отдельный домен или сеть доставки снимают этот предел и разгружают основной сервер.

Смежное

Первоисточник