Отдаем модные форматы картинок в webp и avif напрямую через nginx и apache в обход разметки
Всем привет!
Я тут работал над одним проектом, в котором очень много контентной и интерфейсной графики, десятки тысяч изображений и, конечно, возник вопрос оптимизации сайта, чтобы удовлетворить требования поисковых систем.
Энтузиазма добавили, появившиеся относительно недавно у гугла, так называемые Core Web Vitals.
Кто не в курсе это пачка технических показателей качества сайта, которые скоро будут включены в алгоритм ранжирования и все тормознутые сайты из-за них, типа, покатятся вниз.
Ну в общем, встала задача оптимизировать картинки, а также сделать так, чтобы не пришлось переписывать кучу html кода, чтобы эти картинки туда вставить.
Как это сегодня делается в HTML:
Тут ничего сверхъестественного, просто кладем пачку картинок по нужному адресу, прописываем все варианты изображений в разных форматах и браузер сам выберет, что ему по силам.
Отличный вариант, с прогрессивным подходом — но как только дело доходит до управления такими картинками из админки, начинаются танцы с бубном, большинство дополнений как и штатные тв-поля с типом изображение дают выбирать одно изображение. И получается, чтобы указать все 3 варианта, нужно либо делать 3 тв-шки, либо мутить в разметке что-то подобное с одной тв-шкой
Я тут показал очень простой вариант, предполагающий, что картинка будет точно в .jpg, а ведь могут загрузить и png и jpeg и.т.д И код выше превращается в мешанину регулярок, а когда картинок много — получается кромешный ад.
Потом приходит в голову вынести эту логику в сниппет, чтобы он занимался всем этим, и потом использовать его как модификатор.
Но на этом моменте я уже не выдержал)
Итак решаем задачу «по умному», но сразу скажу, что этот вариант подойдет только для тех, кто имеет доступ к конфигурации web-сервера nginx, то есть большинство шаред-хостингов тут сразу пролетают, так как у них вечно все гайки закручены и раскрутке не подлежат, за исключением modhost.pro и хостингов, где за отдачу статики отвечает apache.
Итак шаг первый:
Сначала надо убедиться, что наш веб-сервер способен понимать файлы с расширением .avif и .webp
Обычно в основном конфиге nginx есть директива include которая подключает файлик содержащий все типы файлов с которыми может работать веб-сервер. Как правило он называется mime.types
Проверяем в нем наличие наших форматов картинок, если их нет добавляем
Шаг второй
Теперь надо добавить проверку заголовка поддержки форматов от браузера, он называется accept и выглядит вот так
Список поддерживаемых форматов будет зависеть от браузера, в данном случае это хром
Проверять этот заголовок мы будем также в основном конфиге в контексте http {}
Добавляем туда
Этот кусок конфига создаст 2 переменные $webp_suffix и $avif_suffix, они будут содержать расширение файла, если браузер его поддерживает или пустую строку если нет.
Шаг третий
Теперь нужно задействовать наши переменные и делать мы это будем уже в конфиге конкретного хоста в контексте server{}
Добавляем нужный локейшн с регуляркой, где фильтруем только картинки в формате jpg и png и дальше начинаем «клеить строки».
set $base $1;
Сначала создается переменная $base которая будет содержать путь до файла без расширенияПример: /assets/images/test
Далее создаем ещё одну переменную $webp_uri которая формируется из склейки предыдущей переменной + переменной, которую мы делали на прошлом шаге — $webp_suffix
set $webp_uri $base$webp_suffix;
Это даст нам строку: /assets/images/test.webpДалее ещё одну переменную по такому же принципу, но приклеиваем другую переменную — $avif_suffix
set $avif_uri $base$avif_suffix;
Это даст нам строку: /assets/images/test.avifadd_header X-uri "$base $webp_uri $avif_uri";
Дальше идет тестовый заголовок, я его добавил вместо того, чтобы выводить инфу о переменных в лог файл, удобно для быстрого тестирования. Этот заголовок выведет всё, что содержится в предыдущих переменных. Его потом нужно будет удалить, когда всё проверите.add_header Vary Accept;
Следом ещё один заголовок — он для кэширования на прокси-серверах, его здесь не буду разбирать.Ну а дальше самая главная строка:
try_files $avif_uri $webp_uri $uri @rewrite;
Директива try_files проверяет доступность адресов по порядку и мы ей говорим следующее:Попробуй найти файл с расширением avif, если такого нет попробуй найти webp, если и такого нет, попробуй обычный формат jpg, png если и их нет отдается обработка в php, но можно и вернуть 404 ошибку, тут кто как настраивает логику работы.
После всего этого, нужно перезапустить nginx, чтобы новые конфиги вступили в силу
И переходим к тестам:
Я сделал фотки во всех нужных форматах
Проверяем, что там нам отдаст браузер
Обратите внимание — слева видно, что запрашивался файл png, но сервер нашел вариант получше и вернул avif
Если я удаляю физически картинку в формате avif, получаем следующее
Теперь получили webp, если и его удалить, то получим изначальную png-шку
Ну и самое главное, теперь в разметке достаточно старого доброго img
Менеджеры в админке выбирают одну картинку, а сервер решает какую сможет показать браузер посетителя.
Вот, собственно, и всё!
А да, совсем, забыл, чтобы постоянно не удалять или перемещать файлы для тестирования, что там отдаст браузер в итоге, можно использовать удобные инструменты отладки во всех хромоподобных браузерах
Просто ставим галку отключения avif — и браузер вдруг разучивается понимать avif картинки, тоже самое с webp
Так тестировать куда приятнее!
P.S:
На одном проекте всё же пришлось решать вопрос со сквозной отдачей картинок через Apache, важно только отметить, что данное решение будет работать если Apache отвечает за отдачу статики, но к сожалению (на самом деле к счастью) на большинстве шаред-хостингов используется комбинация из Apache+Nginx, и за статику отвечает Nginx, так что снова приплыли к основному содержанию статьи) Ну или использовать хостинг — modhost.pro, где можно править конфиг Nginx-а
Короче решение для чистого Apache:
В файлике .htaccess добавляем следующее
<IfModule mod_rewrite.c>
RewriteEngine On
# Если нужно ограничить какой-то директорией, раскомментировать следующую строку
#RewriteCond %{REQUEST_URI} ^/assets/images/
RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{DOCUMENT_ROOT}/$1.avif -f
RewriteRule (.+)\.(jpe?g|png)$ $1.avif [T=image/avif,L,NC]
# Если нужно ограничить какой-то директорией, раскомментировать следующую строку
#RewriteCond %{REQUEST_URI} ^/assets/images/
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{DOCUMENT_ROOT}/$1.webp -f
RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,L,NC]
</IfModule>
<IfModule mod_mime.c>
AddType image/webp .webp
AddType image/avif .avif
</IfModule>
Принцип работы абсолютно такой же, если рядом с оригинальной картинкой будет лежать аналог в webp или avif — веб сервер отдаст именно их, если нет, то оригинальное изображение.
Комментарии: 12
Спасибо! Очень своевременная и важная инструкция.
Познавательно!
Спасибо! Как я понял, здесь указано как работать с {$image}. Не сочтите за наглость, а как правильно прописать конфигурацию конкретного хоста, при использовании {$thumb}? Возможна ли генерация изображений в формате avif?
В статье речь ни о image и ни о thumb, шаблонизатор и доступные плейсхолдеры тут ни при чем. Речь про обработку запросов изображений веб-сервером. Генерировать avif пока нельзя. Насколько знаю phpThumb его пока не поддерживает. Но самому сделать легко, куча сервисов и программ есть. Я их генерирую через nodejs и кладу рядом с оригиналом. То есть MODX не занимается у меня генерацией webp и avif от слова совсем. Все делает нода, формирует эти файлы всегда рядом с тем, что назагружали контент-менеджеры.
Интересно, а скрипт ноды для генерации изображений все время активен на сервере или запускается по расписанию?
Там какой то потоковый сборщик запущен с нужным пайпом или чисто JS скрипт с библиотекой?
Там какой то потоковый сборщик запущен с нужным пайпом или чисто JS скрипт с библиотекой?
Да он висит постоянно, как обычно делают во всяких сборках типа gulp, webpack когда работают с картинками вешают вотчеры, которые колдуют над ними.
Вот такая же фигня запущена на ноде на серваке, важно только там ноду под тем же пользователем запустить, что и весь сайт, а то проблемы с правами поползут.
Я использую для наблюдения — github.com/paulmillr/chokidar
А для генерации любых картинок с любыми настройками — github.com/GoogleChromeLabs/squoosh/tree/dev/cli
Это консольный аналог этого сервиса — squoosh.app/
Вот такая же фигня запущена на ноде на серваке, важно только там ноду под тем же пользователем запустить, что и весь сайт, а то проблемы с правами поползут.
Я использую для наблюдения — github.com/paulmillr/chokidar
А для генерации любых картинок с любыми настройками — github.com/GoogleChromeLabs/squoosh/tree/dev/cli
Это консольный аналог этого сервиса — squoosh.app/
а есть пример скрипта как пережимаются в другие форматы изображения?
Скоро опубликую статью про это, там покажу и скрипт и опишу процесс подробнее.
А есть идеи (подсказка), как этот скрипт использовать на modhost.pro? Конструкцию map нужно запихивать в http {}, а на modhost.pro есть доступ только к редактированию директивы server
Если не ошибаюсь это невозможно в случае modhost. Уж точно не через map
Вот возможно есть способ написать такой скрипт без использования map
К сожалению директива map работает только в контексте http, поэтому её использовать на modhost.pro не получится.
Но можно попробовать другой вариант, правда более медленный, но если получится напишите сюда. Я честно его не пробовал, просто первое, что в голову пришло, но теоретически развить этот вариант можно попробовать
Но можно попробовать другой вариант, правда более медленный, но если получится напишите сюда. Я честно его не пробовал, просто первое, что в голову пришло, но теоретически развить этот вариант можно попробовать
http {
server {
listen 80;
location / {
if ($http_accept ~* "text/html") {
# Do something if the Accept header contains "text/html"
}
}
}
}
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.