Новинка Виртуальный VDS сервер в Финляндии от 390р
Выберите продукт

WebP/AVIF без боли: конверсия и отдача по Accept, Nginx map, кэш и fallback

Практическое руководство для админов и девопсов: ускоряем сайт за счёт WebP/AVIF без поломок. Разбираем предгенерацию, cwebp/avifenc и ImageMagick, Nginx с map и try_files, заголовки Vary/Cache-Control/ETag, корректные MIME, тесты curl и типичные ошибки.
WebP/AVIF без боли: конверсия и отдача по Accept, Nginx map, кэш и fallback

Сжатие изображений — быстрый и надёжный способ ускорить сайт. Сегодня оптимальный набор форматов — это AVIF и WebP, при этом JPEG/PNG остаются «последней линией» совместимости. На практике всё упирается не только в конвертацию, но и в корректную отдачу по заголовку Accept, устойчивый fallback, минимизацию ошибок с кэшированием и MIME. Разберём полный практический стек: конверсия (cwebp/ImageMagick), Nginx map для детекции поддержки, try_files с fallback и грамотные заголовки кэширования.

Зачем WebP/AVIF в 2025 году

Оба формата дают существенную экономию трафика относительно JPEG/PNG при равном визуальном качестве, особенно на мобильных сетях. AVIF обычно выигрывает у WebP по коэффициенту сжатия на сложных сценах, но кодируется медленнее. WebP — компромисс: быстрее кодирование, широко поддерживается, прозрачность и анимация доступны, декодируется легко.

Ключ к безболезненной интеграции — не ломать URL-адреса, не заставлять фронтенд выбирать формат и не плодить тонны правил. Схема с Nginx map и try_files позволяет обслуживать разные форматы из одного и того же пути к изображению, опираясь на Accept и наличие предгенерированных файлов.

Предгенерация vs. конвертация «на лету»

Есть два подхода. Предгенерация (batch-конверсия) хороша предсказуемостью и стабильной задержкой отдачи: все варианты лежат на диске до запроса. Конвертация «на лету» гибче, но сложнее: очередь фоновых задач, защита от штормов, контроль CPU, прогрев кэша. В большинстве случаев достаточно предгенерации для статических каталогов изображений и периодических пересборок.

Если вы настраиваете Nginx под статику, удобнее делать это на управляемом сервере — например, на VDS, где полностью контролируете конфиги и ресурсы.

Надёжный паттерн хранения — «двойное расширение»: к .jpg или .png добавляем суффикс формата. Примеры: photo.jpg.webp и photo.jpg.avif. Это максимально упрощает логику Nginx: можно попробовать отдать $uri.avif, затем $uri.webp, и только потом исходник.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

Конвертация в WebP: cwebp

Пакет cwebp из состава libwebp — стабильный и быстрый инструмент. Базовая команда для JPEG:

cwebp -q 82 -mt -m 6 -sharp_yuv -metadata icc,exif -o photo.jpg.webp photo.jpg

Пояснения к параметрам:

  • -q 82 — типичный старт для фотографии, регулируйте по визуальному порогу.
  • -mt — многопоточность.
  • -m 6 — уровень усилий кодера (6 — максимум в классической шкале).
  • -sharp_yuv — более острый YUV-преобразователь, часто даёт лучшую детализацию.
  • -metadata icc,exif — сохраняем профиль и EXIF при необходимости.

Для PNG с альфой:

cwebp -q 80 -alpha_q 85 -mt -m 6 -metadata icc -o logo.png.webp logo.png

Пакетная конверсия каталога:

find ./images -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \) -print0 | xargs -0 -n1 -P4 -I{} sh -c 'cwebp -q 82 -mt -m 6 -sharp_yuv -metadata icc,exif -o "{}".webp "{}"'

Рекомендация: для фоток начать с q в диапазоне 78–86, для графики с альфой — 75–85 плюс -alpha_q подбирать отдельно. Если в исходных JPEG есть неправильная ориентация, сначала нормализуйте поворот (например, через ImageMagick -auto-orient), затем конвертируйте.

Конвертация в AVIF: avifenc и ImageMagick

AVIF даёт лучший битрейт при том же качестве, но сложнее и медленнее в кодировании. Инструменты: avifenc (libavif, кодек aom) и magick (ImageMagick 7 с поддержкой AVIF через libheif/libavif).

Примеры с avifenc для фотографий:

avifenc --min 20 --max 28 --speed 6 --jobs 4 --codec aom --depth 8 --yuv 420 photo.jpg photo.jpg.avif

Для изображений с прозрачностью и мелкими деталями:

avifenc --min 22 --max 30 --speed 6 --jobs 4 --codec aom --depth 8 --yuv 444 logo.png logo.png.avif

Чем ниже --min/--max — тем выше качество. --speed 6 — разумный компромисс; уменьшение до 4–5 даст плюс в качестве, но кодирование замедлится.

Через ImageMagick:

magick photo.jpg -auto-orient -define avif:codec=aom -define avif:speed=6 -quality 45 photo.jpg.avif
magick logo.png -auto-orient -define avif:codec=aom -define avif:speed=6 -quality 50 logo.png.avif

Шкала -quality у AVIF не эквивалентна JPEG/WebP. Начните с 40–55 и подгоняйте под контент. Для массовой конвертации используйте GNU Parallel или xargs с несколькими потоками, следите за загрузкой CPU и IO.

Организация файлов: простой вариант с «двойным расширением»

Храним photo.jpg, рядом photo.jpg.webp и photo.jpg.avif. Плюсы: не нужно переписывать пути в шаблонах; try_files становится тривиальным; MIME однозначен по последнему расширению. Минусы: занимает больше inode и места, но за счёт сильного сжатия окупается.

Nginx: определяем поддержку через Accept

Браузеры присылают в Accept приоритеты форматов, например image/avif,image/webp,*/*;q=0.8. Для наших задач достаточно проверить наличие подстроки: AVIF приоритетнее WebP, WebP — приоритетнее исходников. Сделаем это через map и соберём финальную переменную с желаемым суффиксом:

map $http_accept $img_avif {
    default 0;
    ~*image/avif 1;
}

map $http_accept $img_webp {
    default 0;
    ~*image/webp 1;
}

map "$img_avif$img_webp" $img_ext {
    default "";
    "10" ".avif";
    "11" ".avif";
    "01" ".webp";
    "00" "";
}

Далее — одна локация для JPEG/PNG, где мы пытаемся отдать соответствующий вариант, а при его отсутствии — исходник. Вариант с «двойным расширением» получается очень компактным:

types {
    image/avif avif;
    image/webp webp;
}

location ~* \.(jpe?g|png)$ {
    try_files $uri$img_ext $uri =404;
    add_header Vary "Accept" always;
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

Как это работает: если клиент поддерживает AVIF, Nginx сначала проверит $uri.avif; если файла нет — проверит $uri.webp (когда $img_ext указывает на WebP); если и этого нет — отдаст исходник. При отсутствии поддержки и AVIF, и WebP $img_ext пуст, и try_files сразу возьмёт оригинал.

Vary и CDN/прокси

Критично добавить Vary: Accept для избежания «перемешивания» вариантов в кэше промежуточных прокси/CDN. Без этого заголовка пользователь, не поддерживающий AVIF, может получить «битое» изображение, закешированное для другого клиента. Параметр always гарантирует заголовок даже при ответах 304.

Кэширование: стратегии и заголовки

Статическим изображениям обычно задаём «долгий» кэш в браузере и CDN. Простейшая и надёжная стратегия — Cache-Control: public, max-age=31536000, immutable. При изменении контента меняем имя файла (хеш в имени или версионирование каталога). Это лучше, чем договариваться об инвалидировании со множеством кэшей.

ETag и Last-Modified — опционально. Если изображения редко переупаковываются и вы используете файловые имена-версии, ETag не обязателен. Если включаете ETag — важно, чтобы он рассчитывался отдельно для каждого варианта (AVIF, WebP, исходник), иначе возможны конфликтные 304. В Nginx для статики по умолчанию выдаётся Last-Modified, этого достаточно в большинстве сценариев.

Для динамических бэкэндов уместны директивы stale-while-revalidate и stale-if-error (при работе через proxy_cache), но для чистой статики они не нужны. Также не стоит применять gzip/brotli к изображениям: они уже сжаты, вы только потратите CPU. Об оптимальном Brotli для текстовых ресурсов мы писали здесь: Brotli и OPCache на хостинге.

CLI-конвертация WebP/AVIF: cwebp и avifenc

Правильные MIME-типы

Добавьте типы для AVIF и WebP, если их нет в вашем наборе MIME. Мы показали блок types выше. Проверьте через curl -I, что на .avif отдаётся Content-Type: image/avif, а на .webpimage/webp. Для двойного расширения (.jpg.avif) тип определяется последним расширением и проблем не возникает.

Тестирование: curl и отладочные заголовки

Имитируем браузер с поддержкой AVIF:

curl -I -H "Accept: image/avif,image/webp" https://example.com/images/photo.jpg

И браузер без современной поддержки:

curl -I -H "Accept: image/png,image/*;q=0.8,*/*;q=0.5" https://example.com/images/photo.jpg

Удобно добавить временный отладочный заголовок, чтобы видеть, какой вариант отдан:

add_header X-Image-Variant $img_ext always;

Не забудьте удалить его после запуска в проде.

Фрагмент конфига Nginx: map и try_files для изображений

Альтернативные схемы хранения и нюансы Nginx

Если хотите хранить конвертаты без «двойных расширений» (например, photo.avif рядом с photo.jpg), логика усложняется: нужно менять расширение в пути. Самый простой способ — не делать этого и придерживаться двойного расширения. Если всё же требуется использовать «чистые» .avif/.webp, придётся собирать кандидатный путь через map и try_files с подстановкой базового имени. Но это менее прозрачно, хуже отлаживается и труднее поддерживается.

Осторожнее с alias: при использовании alias вместо root меняются правила формирования $uri, и try_files может проверять не то, что вы ожидаете. Для простоты храните изображения под тем же root, где и сайт, или корректно перепроверьте пути.

Производительность и ресурсы: как не «поджечь» CPU

AVIF при хорошем качестве кодируется ощутимо дольше WebP. Для массовой переконвертации каталога с десятками тысяч файлов планируйте ночное окно и лимит потоков (-P в xargs или --jobs в avifenc). Имеет смысл разделить задачи: сначала WebP (быстро даёт значимый выигрыш), затем AVIF для ключевых изображений (герои, карточки товаров), а оставшиеся конвертировать постепенно.

От конвертации «на лету» откажитесь, если не можете гарантировать ограничение параллелизма и защиту от штормов кэш-миссов. Для большинства сайтов предгенерация под деплой — самый беспроблемный путь. Если управляете сервером через панель, посмотрите обзор: панели для VDS.

Частые ошибки и как их избежать

  • Забыли Vary: Accept. Симптом: в кэше оказываются «не ваши» варианты, пользователи получают битые изображения. Лечение: добавить add_header Vary "Accept" always;.
  • Неверный MIME. Симптом: Safari/Chrome ругаются или не отображают картинку. Лечение: добавить types { image/avif avif; image/webp webp; }, проверить curl -I.
  • Сломанный путь в try_files. Симптом: 404 для существующих вариантов. Лечение: используйте «двойное расширение» и простое try_files $uri$img_ext $uri =404;.
  • Чрезмерная компрессия. Симптом: артефакты, потеря читабельности. Лечение: поднять качество на 3–5 пунктов, для WebP с альфой отдельно поднять -alpha_q, для AVIF играться c --min/--max или -quality.
  • Проблемы с ориентацией. Симптом: повернутые фото. Лечение: нормализовать поворот перед конвертацией (-auto-orient в ImageMagick), сохранить EXIF при необходимости.
  • Лишняя компрессия gzip/brotli. Симптом: бесполезная нагрузка CPU. Лечение: отключить сжатие для изображений, они уже оптимально упакованы.

SVG, GIF и прочие кейсы

SVG не стоит «прятать» за этой логикой: это вектор, он отлично кэшируется как есть. Анимированный GIF имеет смысл перекодировать в видео/анимацию WebP только при контролируемом фронтенде и тестах совместимости. Для единообразия в Nginx ограничьте локацию на jpg|jpeg|png, не задевая другие типы.

Мини-чеклист перед релизом

  • Сгенерированы .webp и .avif в нужных каталогах для ключевых изображений.
  • Nginx: map настроены, try_files проверен под разными Accept.
  • Отдаём корректные Content-Type для всех расширений.
  • Есть Vary: Accept, заголовки кэша установлены и проверены.
  • curl -I показывает ожидаемые варианты и коды ответов.
  • CDN, если используется, учитывает Vary и не переписывает заголовки.

Итог

Рецепт «без боли» складывается из четырёх частей: предсказуемая предгенерация (cwebp/avifenc или ImageMagick), простое хранение с «двойным расширением», лаконичная связка map + try_files в Nginx и аккуратные заголовки кэширования с Vary: Accept. Такой подход даёт максимальную отдачу от AVIF/WebP без изменений в шаблонах и без рисков сломать обратную совместимость. Настраивается один раз, работает стабильно, легко отлаживается и поддаётся автоматизации в любом конвейере сборки.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...