Предсжатие статики (precompressed assets) — простой и надёжный способ ускорить сайт: вы заранее создаёте файлы .br (brotli) и .gz (gzip) для CSS, JS, HTML, SVG и текстовых ресурсов, а веб‑сервер отдаёт их напрямую в зависимости от Accept-Encoding. Такой подход даёт стабильную скорость ответа и экономит CPU по сравнению с динамической компрессией на лету.
Как это работает: Accept-Encoding и Content-Encoding
Браузер сообщает серверу, какие кодеки поддерживает, заголовком Accept-Encoding (например, br, gzip). Сервер выбирает подходящий вариант файла и возвращает его с заголовком Content-Encoding (br или gzip). Чтобы кэш на пути (прокси, CDN, браузер) различал варианты, нужно добавлять Vary: Accept-Encoding. Это критически важно, иначе клиенту, не поддерживающему brotli, может прилететь неподходящий ответ из кэша.
Коротко: если клиент умеет brotli — отдаём
.br. Если нет, но есть gzip — отдаём.gz. Если ни то ни другое — исходный файл. Всегда помним проVary: Accept-Encoding.
Nginx: предпочтительный путь — gzip_static и brotli_static
Для Nginx идеальный вариант — использовать встроенный модуль http_gzip_static_module и модуль brotli (ngx_brotli) с директивой brotli_static. Эти модули корректно решают главную проблему «ручной» раздачи: Content-Type определяется по запрошенному URI, а не по служебному расширению .br/.gz. Иными словами, отдавая /assets/app.css из файла app.css.br, сервер вернёт правильный Content-Type: text/css.
Проверьте модули:
nginx -V 2>&1 | tr ' ' '\n' | grep http_gzip_static_module
nginx -V 2>&1 | tr ' ' '\n' | grep brotli
Базовый фрагмент конфигурации (HTTP-блок или сервер/локация для статики):
# Включаем статическую раздачу предсжатых файлов
gzip on;
gzip_static on; # использовать *.gz если есть
# brotli требует внешний модуль ngx_brotli
brotli on;
brotli_static on; # использовать *.br если есть
brotli_comp_level 6; # разумный компромисс, актуально для on-the-fly
# Типы, которые целесообразно компрессировать
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
# Локация со статикой
location /assets/ {
root /var/www/site;
add_header Vary Accept-Encoding always; # обязательный Vary
try_files $uri =404;
}
Комментарии:
gzip_static onиbrotli_static onприоритетно отдают предсжатые файлы. Если их нет, сработает динамическоеgzip on/brotli on(если включено), но лучше держать это как страховку, а не основной режим.- Не используйте
gzip_static always, если у вас нет строгой уверенности, что все клиенты понимают gzip: так вы рискуете отдать сжатый контент клиенту без поддержкиgzip. - Директивы
brotli_typesиgzip_typesформируют список MIME-типов для компрессии на лету. Для*_staticони не обязательны, но их уместно держать синхронизированными с вашей политикой — это лучший «универсальный» шаблон. add_header Vary Accept-Encoding always;нужен даже при статической раздаче, чтобы кэши не путали варианты.
Если brotli-модуль недоступен
Частая ситуация на дистрибутивах или в сборках без ngx_brotli: подключайте только gzip_static on и продолжайте отдавать .gz как precompressed. Это всё равно даёт ощутимую экономию трафика и CPU. Позже можно добавить brotli. На собственном VDS проще контролировать сборку Nginx и подключить ngx_brotli, если он отсутствует в репозитории.
gzip on;
gzip_static on;
# без brotli: директивы brotli* опускаем
location /assets/ {
root /var/www/site;
add_header Vary Accept-Encoding always;
try_files $uri =404;
}
Обходные приёмы с try_files $uri.br без модуля brotli приводят к неверному Content-Type, потому что Nginx определяет его по реальному расширению файла (.br). На стороне Nginx нет штатного механизма привязать text/css к .css.br, поэтому для корректной раздачи brotli используйте именно brotli_static.
HTTP/2 и HTTP/3
Сжатие — orthogonal к протоколу: будь то HTTP/1.1, HTTP/2 или HTTP/3 (QUIC), алгоритм выбора по Accept-Encoding одинаков. Главное — корректно проставлять Content-Encoding и Vary. Предсжатие особенно выгодно при HTTP/3, где латентность важна и CPU лучше потратить на TLS и обработку запросов, а не на компрессию. Для стабильной поддержки TLS и HTTP/3 пригодятся действующие SSL-сертификаты.
Apache: гибко и без плясок с бубном
В Apache предсжатая раздача особенно удобна благодаря поддержке «многочастных» расширений. Для файла style.css.br тип контента берётся из расширения .css, а .br добавляет кодек. То есть достаточно объявить, как трактовать каждый суффикс.
Минимальный набор модулей: mod_mime, mod_headers, mod_rewrite (для удобной переадресации на нужный вариант), опционально mod_brotli/mod_deflate для компрессии на лету.
Вариант с .htaccess
<IfModule mod_mime.c>
AddType text/css .css
AddType application/javascript .js
AddType text/html .html
AddType image/svg+xml .svg
AddEncoding br .br
AddEncoding gzip .gz
</IfModule>
<IfModule mod_headers.c>
Header merge Vary Accept-Encoding
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# Сначала brotli, если поддерживается
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}\.br -s
RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.br [QSA]
# Затем gzip
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.gz [QSA]
</IfModule>
Пояснения:
AddEncoding br .brиAddEncoding gzip .gzобеспечивают правильныйContent-Encoding.AddTypeназначает ожидаемые MIME-типы исходным расширениям. Для предсжатого.css.brApache автоматически возьмётtext/cssпо.cssи добавитContent-Encoding: brпо.br.Header merge Vary Accept-Encodingдобавит/объединит заголовок Vary, не дублируя его при многократной обработке.
Вариант в VirtualHost
<VirtualHost *:80>
ServerName example.test
DocumentRoot /var/www/site/public
<IfModule mod_mime.c>
AddType text/css .css
AddType application/javascript .js
AddType text/html .html
AddType image/svg+xml .svg
AddEncoding br .br
AddEncoding gzip .gz
</IfModule>
<IfModule mod_headers.c>
Header merge Vary Accept-Encoding
</IfModule>
<Directory /var/www/site/public>
AllowOverride All
Require all granted
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}\.br -s
RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.br [QSA]
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.gz [QSA]
</IfModule>
</VirtualHost>
Такой конфиг безопасно и прозрачно раздаёт предсжатые варианты. Если для ресурса нет .br/.gz, отдаётся обычный файл.

Какие типы файлов компрессировать
Компрессия полезна для текстовых форматов: CSS, JS, HTML, JSON, SVG, XML, TXT, CSV, WebManifest и т.п. Для уже сжатых бинарных форматов (PNG, JPEG, WebP, MP4, PDF) выигрыша нет или он минимален, а иногда это вредно: сжатый бинарник часто становится больше из‑за overhead. В конфиге Nginx это отражается через списки gzip_types и brotli_types, в Apache — через AddType (для сопоставления MIME) и логику переписывания URI.
Генерация .br и .gz: локально и в CI/CD
Добавьте этап предсжатия в сборку. Для больших проектов это делается на уровне бандлера (Webpack, Rollup, Vite, Parcel) плагинами, для статических сайтов — отдельной утилитой. Примеры CLI:
# Brotli максимального качества (дольше, но наилучший размер)
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} brotli -f -q 11 {}
# Gzip разумного компромисса
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} gzip -f -k -9 {}
# Альтернатива для *.gz: zopfli (ещё меньше, но медленнее)
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} zopfli --i1000 {}
Замечания:
- Не сжимайте двоичные уже‑сжатые форматы: картинки, видео, архивы. Это лишний CPU и мусор в репозитории.
- Убедитесь, что сборка не перезаписывает
.br/.gzпосле минификации: сжимайте после финального шага минификации и хеширования файлов. - На CI полезно кешировать артефакты между билдами, чтобы не компрессировать неизменённые файлы.

Отладка и проверка
Проверьте заголовки и размер ответа через curl:
# Проверка brotli
curl -I -H "Accept-Encoding: br" https://example.test/assets/app.css
# Проверка gzip
curl -I -H "Accept-Encoding: gzip" https://example.test/assets/app.css
# Проверка без компрессии
curl -I -H "Accept-Encoding:" https://example.test/assets/app.css
Смотрите на Content-Encoding, Vary, Content-Length. Для предсжатых ответов размер должен совпадать с размером соответствующих .br/.gz файлов. В браузере удобны DevTools: вкладка Network, колонки Encoded/Decoded size, Response Headers.
Кэширование: Vary, ETag, Cache-Control
Для корректной работы кэшей:
Vary: Accept-Encodingобязателен.- ETag и/или
Last-Modifiedдля предсжатых вариантов будут отличаться от исходных. Это нормально: у каждого варианта свойETag. Подробно про политику кэширования статики — в статье Cache-Control и ETag для статики. - Задайте
Cache-Controlдля статики (например, длинный max-age +immutableдля файлов с хешами). Это orthogonal к компрессии, но критично для производительности. - С CDN проверьте, что оно уважает
Varyили умеет варьировать кэш поAccept-Encodingавтоматически. В большинстве современных CDN это уже решено.
Тонкости безопасности и совместимости
Предсжатие безопасно для статических ресурсов. Риски компрессионных атак (BREACH и т.п.) относятся к динамическому сжатию ответов, содержащих секреты и отражающие пользовательский ввод. Для статики это неактуально. Тем не менее:
- Не включайте принудительный
gzip_static always— это может сломать клиентов/ботов без поддержки gzip. - Не применяйте компрессию к архивам, двоичным файлам и медиа.
- Следите, чтобы кросс-оригинные шрифты и важные статики отдавались с корректными типами (
typesв Nginx,AddTypeв Apache). Неверный MIME ломает поведение браузера сильнее, чем отсутствие компрессии. Рекомендую также настроить базовые заголовки безопасности: смотрите шпаргалку HTTP security headers в Nginx и Apache.
Частые ошибки
- Забыли
Vary: Accept-Encoding. Итог: кэш отдаёт gzip клиенту без поддержки. - Ручной
try_filesна$uri.brв Nginx безbrotli_static. Итог: неправильныйContent-Type. - Пересжатие уже сжатых форматов. Итог: файлы больше, CPU тратится зря.
- Генерация
.br/.gzдо минификации/хеширования. Итог: компрессию надо повторять заново. - Неверный список
gzip_types/brotli_typesилиAddType. Итог: часть статики не сжимается на лету, либо уходит с неверным типом.
Итоги
Предсжатие статики .br/.gz — один из самых простых и предсказуемых приёмов ускорения. В Nginx стремитесь использовать gzip_static и brotli_static — это гарантирует корректный Content-Type и минимальный overhead. В Apache достаточно объявить AddEncoding и AddType и аккуратно перенаправить запросы через mod_rewrite. Не забывайте про add_header Vary Accept-Encoding, поддерживайте актуальные списки types и встроите генерацию precompressed артефактов в сборку. Результат — мгновенная отдача и меньшая нагрузка на сервер под пиковыми нагрузками.


