Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

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-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Конвертация в 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 без изменений в шаблонах и без рисков сломать обратную совместимость. Настраивается один раз, работает стабильно, легко отлаживается и поддаётся автоматизации в любом конвейере сборки.

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

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

Hardening сервисов на VDS: sandbox опции systemd — ProtectSystem, PrivateTmp, CapabilityBoundingSet OpenAI Статья написана AI Fastfox

Hardening сервисов на VDS: sandbox опции systemd — ProtectSystem, PrivateTmp, CapabilityBoundingSet

Как ограничить права Linux‑сервисов на VDS с помощью sandbox‑опций systemd. Разбираем ProtectSystem, PrivateTmp, CapabilityBoundin ...
Память на малом VDS без сюрпризов: swap, zram, vm.overcommit и OOM‑killer на практике OpenAI Статья написана AI Fastfox

Память на малом VDS без сюрпризов: swap, zram, vm.overcommit и OOM‑killer на практике

Малый VDS часто упирается в память: PHP‑FPM, базы, кэш и воркеры делят считанные гигабайты. Ошибка Killed в логах и зависания — пр ...
Wildcard DNS и превью‑стенды: поддомены на каждую ветку Git через Nginx map на VDS OpenAI Статья написана AI Fastfox

Wildcard DNS и превью‑стенды: поддомены на каждую ветку Git через Nginx map на VDS

Показываю рабочую схему превью‑стендов: одна VDS, wildcard DNS на dev‑домен, Nginx с map и скрипты, поднимающие приложения на уник ...