Оптимизация изображений в 2026 году сводится к трём вещам: формат AVIF с фолбэком на WebP, корректный srcset c sizes и явная приоритизация LCP-картинки через fetchpriority="high". Картинки занимают 40–60% веса средней страницы и почти всегда оказываются Largest Contentful Paint элементом, поэтому каждый сэкономленный килобайт и каждая миллисекунда раньше старта загрузки напрямую улучшают Core Web Vitals. В этом гайде собрал рабочие команды, разметку и трассы из DevTools, которые я применяю на продакшене (некоторые приёмы вытащил из боли последних трёх релизов).
AVIF даёт на 30–50% меньше размер по сравнению с WebP при том же визуальном качестве; поддержка браузеров в 2026 году достигла 96.3% по Can I Use.
LCP-изображение должно загружаться через <img> с fetchpriority="high" и без loading="lazy". Это единственный способ обогнать CSS и JS в очереди приоритетов.
Атрибуты width и height на каждом <img> обязательны: они резервируют место и убирают CLS до того, как картинка успеет скачаться.
Адаптивные изображения через <picture> с тремя форматами (AVIF, WebP, JPEG) экономят 200–600 КБ на мобильных устройствах.
Sharp 0.34 и Squoosh CLI кодируют AVIF в 4–6 раз быстрее, чем libavif напрямую, что меняет экономику CI-пайплайна.
Декодирование больших картинок блокирует main thread; decoding="async" и content-visibility: auto переносят работу с него.
AVIF против WebP в 2026: что выбрать
Короткий ответ: AVIF как основной формат, WebP как фолбэк, JPEG/PNG как последняя ступень. По состоянию на июнь 2026 поддержка AVIF в браузерах составляет 96.3% (Chrome 85+, Firefox 113+, Safari 16+, включая стабильную поддержку на iOS 16+). WebP остаётся в 99.1% браузеров и нужен для устаревших iOS 15 и редких корпоративных Edge-сборок.
На моих стендах AVIF выигрывает у WebP по размеру при равном SSIM ≥ 0.97 примерно на 38% для фотографий и на 22% для иллюстраций с плоскими цветами. По сравнению с JPEG (q=80) AVIF при cq-level=28 даёт около 55% экономии. Цена за это: кодирование. cavif и libavif при пресете effort=4 работают в 6–10 раз медленнее cwebp; это надо учитывать в CI и предпочесть Sharp 0.34, который завернул AVIF в нативные bindings с SIMD.
Параметр
AVIF
WebP
JPEG (mozjpeg)
Поддержка браузеров (2026)
96.3%
99.1%
100%
Размер vs JPEG q=80
−55%
−30%
baseline
Скорость кодирования
медленно
быстро
очень быстро
Альфа-канал
да
да
нет
HDR / широкий цвет
да (10/12 бит)
нет
нет
Прогрессивное декодирование
да
нет
да
Лучшее применение
фото, hero, LCP
UI-графика
универсальный фолбэк
Практический рецепт: всегда генерируйте все три формата и отдавайте через <picture>. Не пытайтесь обойтись «только AVIF». Даже 3–4% посетителей без поддержки увидят сломанное изображение, и это будет ровно то подмножество, которое жалуется громче всех.
Как ускорить загрузку LCP-изображения
LCP-картинка, по сути, главная мишень оптимизации. На большинстве лендингов это hero-изображение, которое попадает в viewport за первые 600–1200 мс. По данным web.dev по LCP, разница между «хорошим» (≤2.5 с) и «плохим» (>4 с) LCP в 78% случаев объясняется задержкой обнаружения и старта загрузки именно этого ресурса, а не его размером.
Минимальный рабочий набор для LCP-изображения в 2026 году:
<!-- В head: для критичных hero-картинок, известных на этапе HTML -->
<link rel="preload"
as="image"
href="/hero-1280.avif"
imagesrcset="/hero-640.avif 640w, /hero-1280.avif 1280w, /hero-1920.avif 1920w"
imagesizes="100vw"
fetchpriority="high"
type="image/avif">
<!-- В разметке -->
<picture>
<source type="image/avif"
srcset="/hero-640.avif 640w, /hero-1280.avif 1280w, /hero-1920.avif 1920w"
sizes="100vw">
<source type="image/webp"
srcset="/hero-640.webp 640w, /hero-1280.webp 1280w, /hero-1920.webp 1920w"
sizes="100vw">
<img src="/hero-1280.jpg"
srcset="/hero-640.jpg 640w, /hero-1280.jpg 1280w, /hero-1920.jpg 1920w"
sizes="100vw"
width="1920" height="1080"
alt="Описание для скринридеров"
fetchpriority="high"
decoding="async">
</picture>
Три критичные детали, которые часто упускают. Первая: fetchpriority="high" ставится и на <link rel="preload">, и на <img>, потому что это два разных события в очереди браузера. Вторая: никогда не вешайте loading="lazy" на LCP-картинку, даже если она «как бы ниже фолда», иначе браузер отложит загрузку до layout-этапа и LCP уедет на 300–800 мс. Третья: type в preload обязателен, иначе Chrome дублирует загрузку AVIF и JPEG.
Если вы уже работаете с заголовками сервера, оптимальная пара такая: Preload Link плюс 103 Early Hints. Я подробнее разбирал это в материале про приоритизацию загрузки ресурсов и Early Hints; для LCP-изображений это даёт ещё 50–150 мс выигрыша на холодном TLS.
srcset и sizes: правильная разметка
Адаптивные изображения экономят больше всего трафика и времени декодирования. Мобильному устройству с экраном 390 CSS px не нужна картинка 1920 px (она сожжёт 250–500 мс на скачивание и декодирование). Правильный srcset + sizes позволяет браузеру выбрать ресурс под фактический размер контейнера и DPR.
Базовая семантика. srcset описывает доступные варианты с их естественной шириной (w-дескриптор). sizes описывает, как контейнер картинки будет занимать viewport. Браузер умножает sizes на devicePixelRatio, находит ближайший вариант из srcset и качает только его.
Типичная ошибка: sizes="100vw" у картинки, которая на десктопе занимает половину контейнера. Браузер качает в два раза больше, чем нужно. Честно говоря, я сам ловил такие случаи на собственных проектах через RespImageLint; он находит расхождение между объявленным sizes и реальной шириной элемента на разных breakpoint-ах.
Правило плотности через дескриптор x (1x, 2x, 3x) используйте только для иконок и UI-графики, где размер фиксирован. Для всего остального используйте дескриптор w, иначе вы оптимизируете под одно устройство.
Элемент picture и фолбэки форматов
Элемент <picture> в 2026 году выполняет две задачи: фолбэки форматов и art direction (разная композиция для разных экранов). Браузер выбирает первый <source>, у которого type поддерживается и media подходит. Если ни один не подошёл, отдаёт <img>.
Важная деталь: width и height на <img> внутри <picture> с art direction должны соответствовать соотношению сторон того варианта, который реально будет показан в большинстве сессий. Иначе CLS на медленных сетях достигает 0.2+. Для двух разных aspect-ratio используйте aspect-ratio в CSS вместе с media query.
Предотвращение CLS: width, height и aspect-ratio
Cumulative Layout Shift на изображениях возникает, когда браузер до загрузки картинки не знает её итоговых размеров и схлопывает контейнер в 0. Когда картинка приезжает, контент ниже прыгает. Решение известно с 2020 года, и тем не менее половина команд до сих пор пропускает шаг. Я сам забыл это на одном PR в 2024-м и получил CLS 0.31 в проде, что стоило отдельного откатного релиза.
Правильно так:
<!-- Атрибуты width и height задают соотношение сторон -->
<img src="/photo.avif"
width="1200"
height="800"
alt="...">
/* CSS оставляет картинку отзывчивой, не теряя aspect-ratio */
img {
max-width: 100%;
height: auto;
}
Современный браузер вычисляет aspect-ratio: 1200 / 800 из атрибутов и резервирует место до загрузки. Если картинка обрезается через CSS (object-fit: cover) и реальные размеры не совпадают с атрибутами, всё равно используйте те значения, которые соответствуют визуальному прямоугольнику в layout. CLS от изображений напрямую связан с другими CLS-источниками, особенно со шрифтами; я разбирал это в гайде про оптимизацию шрифтов и устранение CLS.
Lazy loading: когда применять, а когда нет
Атрибут loading="lazy" ставится на все картинки ниже первого экрана. Это нативный механизм, и Chrome, Firefox и Safari реализуют его через IntersectionObserver под капотом, без всякого JavaScript. Эффект: hero и видимый контент скачиваются первыми, а изображения в футере и нижних секциях откладываются до приближения к viewport.
Никогда не ленизируйте LCP-картинку. Браузер откладывает её обнаружение до layout-фазы, что добавляет 200–800 мс к LCP.
Не ленизируйте первые 1–2 изображения галереи, если они в viewport на типичном экране 1366×768.
Для iframe используйте loading="lazy" на всех встраиваниях (YouTube, карты, виджеты) без исключений. Часто это самая большая экономия CPU.
Image CDN или Sharp в CI: что выбрать
Есть два главных подхода к генерации вариантов: статические в CI-пайплайне или динамические через image CDN. Я применяю оба, и выбор зависит от количества SKU и частоты обновления.
Статический пайплайн на Sharp 0.34 — это 30–60 строк кода, ноль внешних зависимостей и полностью кэшируемый CDN-слой. Подходит для контентных сайтов, документации, лендингов с десятками-сотнями изображений:
// build/optimize-images.mjs
import sharp from 'sharp';
import { readdir, mkdir } from 'node:fs/promises';
import { join, extname, basename } from 'node:path';
const WIDTHS = [400, 800, 1200, 1600, 1920];
const SRC = './src/images';
const OUT = './public/img';
const files = (await readdir(SRC)).filter(f => /\.(jpg|png)$/i.test(f));
await mkdir(OUT, { recursive: true });
for (const file of files) {
const name = basename(file, extname(file));
const input = sharp(join(SRC, file));
const meta = await input.metadata();
for (const w of WIDTHS) {
if (w > meta.width) continue;
const resized = input.clone().resize({ width: w });
await Promise.all([
resized.clone().avif({ quality: 50, effort: 6 })
.toFile(join(OUT, `${name}-${w}.avif`)),
resized.clone().webp({ quality: 80, effort: 5 })
.toFile(join(OUT, `${name}-${w}.webp`)),
resized.clone().jpeg({ quality: 82, mozjpeg: true, progressive: true })
.toFile(join(OUT, `${name}-${w}.jpg`)),
]);
}
}
На моих проектах этот скрипт обрабатывает 200 изображений за 90–120 секунд на 8-ядерной CI-машине. Sharp параллелит на native потоках, не упираясь в Node event loop.
Динамический image CDN (Cloudinary, ImageKit, Bunny Optimizer, Cloudflare Images) выигрывает там, где изображений тысячи и они приходят от пользователей: маркетплейсы, UGC-платформы. Цена: $0.5–3 за 1000 трансформаций плюс CDN-трафик. Архитектурно это всегда дороже статики, но снимает с команды задачу оркестрации.
Декодирование и рендеринг: вторая половина бюджета
Скачать картинку — это половина работы. Декодирование AVIF большого разрешения занимает 30–120 мс на main thread мобильного устройства среднего класса. По нашим трассам в Performance, это часто вылезает в Long Animation Frames и портит INP. Атрибут decoding="async" просит браузер декодировать вне main thread и не показывать картинку, пока она не готова. Это убирает janks при скролле:
Для длинных страниц с галереями применяйте CSS content-visibility: auto, чтобы браузер пропускал layout и paint для секций вне viewport. На страницах с 50+ карточек это даёт ускорение first paint в 3–7 раз; детали в материале про content-visibility и Containment API. Сочетание decoding="async" плюс content-visibility: auto плюс loading="lazy" — это стандартный стек для длинных лент в 2026 году.
Как замерить эффект и поймать регрессии
Измеряйте до и после в трёх местах: лабораторно (Lighthouse 12 / PageSpeed Insights), на реальных пользователях (RUM через web-vitals.js или CrUX) и в CI на каждый PR.
Минимальный snippet для RUM-измерения LCP с разбивкой по элементу:
LCP element URL в RUM. Если URL «гуляет» между релизами, значит у вас не одна LCP-картинка, а несколько, и кэш отдыхает.
loadDelay. Любое значение выше 100 мс говорит о пропущенном preload или забытом fetchpriority.
renderDelay. Выше 200 мс значит, что браузер не успел распаковать картинку до paint. Проверьте размер AVIF (часто overcompressed, отсюда медленное декодирование) и decoding="async".
В CI прогоняйте Lighthouse через @unlighthouse/cli на превью-деплое каждого PR с порогами: LCP ≤ 2.0 s, CLS ≤ 0.05, размер главной картинки ≤ 120 КБ. Это ловит 90% регрессий до релиза.
Часто задаваемые вопросы
Какой формат изображения лучше всего использовать в 2026 году?
AVIF как основной формат для всех новых сайтов, WebP как фолбэк для 3–4% устаревших браузеров, JPEG/PNG как универсальный последний рубеж. Отдавайте все три через <picture>, и браузер сам выберет первый поддерживаемый.
Почему моя LCP-картинка загружается медленно, даже если она маленькая?
В 95% случаев проблема не в размере файла, а в задержке обнаружения. Браузер начинает качать LCP-картинку только после парсинга HTML и расчёта layout. Добавьте <link rel="preload" as="image" fetchpriority="high"> в head и fetchpriority="high" на сам <img>.
Нужно ли указывать width и height, если я задаю размер через CSS?
Да, обязательно. Атрибуты width и height используются браузером для вычисления aspect-ratio и резервирования места до загрузки картинки. Без них вы получите Cumulative Layout Shift, даже если CSS правильный.
Можно ли применять loading="lazy" ко всем изображениям сразу?
Нет. Lazy-loading LCP-картинки и изображений выше первого экрана откладывает их обнаружение до layout-фазы и ухудшает LCP на 200–800 мс. Применяйте loading="lazy" только к изображениям, которые гарантированно ниже фолда на всех устройствах.
Что лучше для оптимизации изображений: image CDN или статика в CI?
Для контентных сайтов и лендингов с сотнями изображений лучше статика на Sharp в CI: предсказуемая стоимость, 100% кэшируемость, ноль внешних зависимостей. Для UGC-платформ и маркетплейсов с тысячами картинок и динамическими трансформациями подойдёт image CDN типа Cloudflare Images или Cloudinary.
Как уменьшить размер изображений без потери качества?
Кодируйте AVIF с quality: 50, effort: 6 через Sharp 0.34. Это даёт визуально неотличимое от исходника качество при размере на 55% меньше JPEG q=80. Дополнительно: ресайз до реально нужной ширины (не больше 2× от CSS-размера) и использование srcset с дескрипторами w.
Полный разбор Service Worker и стратегий кэширования в 2026: Workbox 7, stale-while-revalidate для статики, NetworkFirst для HTML, Navigation Preload и офлайн-фолбэки с рабочими примерами кода.
Шрифты — самая недооценённая причина плохих Core Web Vitals в 2026 году. Разбираем, как variable fonts, font-display, size-adjust, subsetting и 103 Early Hints полностью убирают CLS и FOIT и ускоряют LCP на 30–60%.
Как уменьшить JavaScript-бандл в Vite и Webpack: tree shaking, code splitting, vendor splitting и анализ размера. Рабочие примеры конфигов, чек-лист из 10 шагов и реальные кейсы Dropbox и redBus.