Обсудить задачу

LCP: оптимизация изображений - AVIF, WebP, srcset

LCP (Largest Contentful Paint) почти всегда упирается в изображение: на подавляющем большинстве маркетинговых и контентных сайтов LCP-элементом становится hero-картинка, а не текст. Чтобы уложиться в порог 2.5 секунды, нужно решить четыре задачи одновременно: выбрать формат (AVIF или WebP вместо JPEG), отдать браузеру нужный размер через srcset и sizes, поднять приоритет загрузки через fetchpriority="high" и не сломать всё лишним loading="lazy". Дальше - конкретные цифры сжатия, рабочий код и разбор того, как эту задачу решают Astro Image, Next.js Image и ручная вёрстка.

В проектах по оптимизации сайтов на Astro и Next.js мы регулярно видим одну и ту же картину: PageSpeed показывает LCP 3.5-4.5 секунды, а разработчик уже подключил лёгкий фреймворк, вырезал лишний JavaScript и настроил CDN. Проблема не в стеке - hero-изображение весит 1-2 МБ в JPEG, отдаётся без srcset, без fetchpriority, а иногда даже с loading="lazy" на элементе, который браузер обязан загружать в первую очередь.

Русскоязычные материалы на эту тему обычно упоминают WebP одним пунктом в общем списке SEO-советов - без сравнения с AVIF, без цифр сжатия и без кода для srcset/sizes. Эта статья закрывает именно этот пробел: формат, реализация, приоритет загрузки и то, что автоматизируют за вас Astro и Next.js.

Медленный LCP напрямую бьёт по конверсии и позициям: Google использует Core Web Vitals как фактор ранжирования, а пользователи закрывают вкладку, если главный экран не прорисовался за 2.5-3 секунды. На лендингах и маркетинговых страницах, где hero-изображение занимает всю область видимости, это не абстрактная метрика - это первая секунда контакта с брендом, за которую отвечает один HTTP-запрос.

LCP (Largest Contentful Paint): время до отрисовки самого крупного видимого элемента на экране - в большинстве случаев это hero-изображение или блок с фоновой картинкой. Порог «хорошо» - менее 2.5 секунды, «плохо» - больше 4 секунд.

Почему LCP - это почти всегда изображение

Перед тем как оптимизировать формат и приоритет, нужно точно знать, какой элемент Google считает LCP на конкретной странице - это не всегда очевидно.

В PageSpeed Insights и Lighthouse есть раздел «Largest Contentful Paint element» - он прямо указывает на DOM-узел, который стал LCP. В Chrome DevTools это вкладка Performance: на треке Timings появляется маркер LCP, клик по которому подсвечивает элемент на странице. Чаще всего это <img> в hero-секции, реже - крупный текстовый блок или background-image в CSS.

Полный разбор методологии измерения - CrUX против Lighthouse, пороговые значения по всем трём метрикам Core Web Vitals - мы уже разбирали в статье про оптимизацию Core Web Vitals на WordPress. Здесь фокус только на LCP и только на изображениях, потому что именно они определяют результат на 80-90% сайтов с hero-баннером или обложкой товара. CLS и INP эта статья не покрывает - про INP отдельно есть разбор оптимизации INP.

AVIF vs WebP vs JPEG: цифры сжатия

Выбор формата - это первая и самая весомая по эффекту точка оптимизации LCP, потому что вес файла напрямую определяет время скачивания.

AVIF: формат на основе кодека AV1, сжимает изображения примерно на 50% сильнее JPEG и на 20-30% сильнее WebP при сопоставимом визуальном качестве, особенно на фотографиях и градиентах. Поддержка браузеров в 2026 году - выше 95% глобального трафика, Safari получил поддержку в версии 16.4 ещё в марте 2023 года.

WebP: сжимает на 25-35% сильнее JPEG, поддерживается практически повсеместно - около 97% браузеров. Кодируется в 5-10 раз быстрее AVIF, что важно для сайтов с пользовательской загрузкой изображений в реальном времени.

JPEG: остаётся нужен только как fallback для устаревших браузеров и почтовых клиентов - в вебе 2026 года как основной формат не оправдан.

ФорматСжатие относительно JPEGПоддержка браузеровСкорость кодированияКогда использовать
JPEGбаза~100%быстроfallback
WebP-25…-35%~97%быстробезопасный дефолт для всех изображений
AVIF-50% (фото и градиенты - больше)~95%+в 5-10 раз медленнее WebPhero-изображение, LCP-элемент, высоконагруженные страницы

Практический вывод: WebP - безопасный дефолт для всей галереи изображений на сайте. AVIF имеет смысл целенаправленно применять к hero-изображению - единственному элементу, который определяет LCP, где выигрыш в 20-30% веса напрямую конвертируется в секунды загрузки. Кодировать в AVIF весь фотобанк сайта в реальном времени - плохая идея из-за скорости кодирования, но для статической генерации на этапе сборки (Astro, Next.js со статическим экспортом) это не проблема: кодирование происходит один раз при деплое, а не при каждом запросе пользователя.

Браузер сам выбирает лучший поддерживаемый формат через тег <picture> с несколькими источниками:

Пример: picture с AVIF, WebP и JPEG fallback
<picture>
  <source srcset="/hero.avif" type="image/avif">
  <source srcset="/hero.webp" type="image/webp">
  <img src="/hero.jpg" alt="Описание" width="1200" height="630" fetchpriority="high">
</picture>

Браузер проверяет источники сверху вниз и загружает первый поддерживаемый формат. Атрибут fetchpriority указывается на финальном <img>, а не на <source>.

Responsive images на практике: srcset и sizes

Вторая по значимости ошибка после формата - отдавать один и тот же файл всем устройствам, независимо от ширины экрана.

Если hero-изображение весит 400 КБ в разрешении 3000px и отдаётся телефону с шириной экрана 375px, браузер скачивает файл полностью, а затем сжимает его в CSS - это чистая трата трафика и времени до LCP. Атрибуты srcset и sizes решают задачу нативным HTML без единой строки JavaScript.

srcset перечисляет доступные варианты файла с их шириной в дескрипторе w. sizes сообщает браузеру, какую ширину изображение займёт на экране при разных условиях viewport - именно на основе этой информации браузер выбирает подходящий файл из srcset ещё до начала загрузки CSS.

Пример: img с srcset и sizes для hero-изображения
<img
  src="/hero-1200.webp"
  srcset="/hero-480.webp 480w, /hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1920.webp 1920w"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
  alt="Описание"
  width="1200"
  height="630"
  fetchpriority="high"
>

Оптимальное количество вариантов в srcset - 3-5. Больше вариантов увеличивает нагрузку на кэш CDN и сборку, меньше - снижает точность подбора размера под устройство.

Частая ошибка - указать srcset, но забыть sizes. Без sizes браузер по умолчанию считает, что изображение займёт 100% ширины viewport, и в большинстве случаев подбирает файл крупнее, чем нужно на самом деле - выигрыш от responsive images обнуляется.

fetchpriority=“high” и preload без смены формата

Даже идеально сжатое AVIF-изображение может стать причиной плохого LCP, если браузер узнаёт о нём слишком поздно - и здесь вступает в игру приоритет загрузки.

Браузер строит preload scanner - механизм, который сканирует ещё не полностью загруженный HTML в поисках ресурсов для скачивания, не дожидаясь построения полного DOM. Обычные изображения браузер загружает с обычным приоритетом наравне с десятками других ресурсов на странице. Атрибут fetchpriority="high" явно указывает браузеру: этот ресурс важнее остальных, скачивай его немедленно.

В собственных тестах Google (Addy Osmani, Chrome DevRel) добавление одного атрибута fetchpriority="high" на LCP-изображение улучшило LCP с 2.6 до 1.9 секунды - без изменения формата, размера или хостинга. Правило простое: атрибут ставится ровно на одно изображение на странице - то, которое действительно является LCP-элементом. Если пометить пять картинок как приоритетные, браузер не понимает, какая из них реально важна, и выигрыш исчезает.

Для критичных случаев (например, изображение, которое браузер обнаруживает только после загрузки CSS с background-image) можно явно предзагрузить ресурс через <link rel="preload"> в <head>:

Пример: preload для LCP-изображения в head
<link rel="preload" as="image" href="/hero-1200.webp" fetchpriority="high">

Если изображение уже используется через srcset, современные браузеры поддерживают атрибуты imagesrcset и imagesizes прямо в теге preload, чтобы предзагрузка учитывала тот же набор вариантов.

loading=“lazy” ломает LCP - типичная ошибка

Атрибут loading="lazy" придуман для изображений ниже первого экрана - и именно попытка применить его «на всякий случай» ко всем картинкам на странице чаще всего портит LCP.

Когда браузер встречает loading="lazy", preload scanner намеренно пропускает этот ресурс при первом проходе по HTML - загрузка откладывается до момента, когда элемент приближается к области видимости при скролле. Для hero-изображения, которое пользователь видит в первую секунду, это создаёт задержку в 300 миллисекунд - 1.5 секунды, в зависимости от того, насколько поздно браузер обнаруживает элемент повторно.

Правило без исключений: LCP-изображение получает loading="eager" (или атрибут вообще не указывается - это значение по умолчанию) в паре с fetchpriority="high". loading="lazy" оставляется строго для изображений ниже первого экрана - в галереях, карточках товаров, футере.

Частый источник этой ошибки - CMS и билдеры, которые автоматически проставляют loading="lazy" всем <img> на странице через глобальную настройку темы или плагина, не различая hero-блок и остальной контент. Это стоит проверить в первую очередь, если LCP просел после смены темы или добавления нового плагина оптимизации изображений.

Astro Image, Next.js Image или ручная вёрстка

Весь код выше можно написать руками, но современные фреймворки автоматизируют большую часть этой работы - вопрос в том, какой уровень контроля вам нужен.

Astro <Image /> и <Picture />. Компонент из astro:assets конвертирует изображение в WebP по умолчанию на этапе сборки, без обращения к рантайм-API. Для нескольких форматов сразу используется <Picture formats={['avif', 'webp']} /> - Astro сгенерирует статические файлы всех форматов при деплое, без нагрузки на сервер при каждом запросе. Это отличает Astro от инструментов с рантайм-конвертацией: оптимизация происходит один раз при сборке, а не на каждый визит пользователя.

Next.js <Image />. Компонент next/image по умолчанию проксирует изображения через встроенный API оптимизации на лету, если сайт не собирается в полностью статический экспорт. Для LCP-изображения нужно явно передать проп приоритета (priority в актуальных версиях, начиная с Next.js 16 - preload) - именно он вставляет <link rel="preload"> в <head> автоматически. Проп sizes для layout с fill обязателен - без него изображение скачивается в ширину всего viewport независимо от реального размера контейнера.

Ручная вёрстка. Оправдана, когда сайт не построен на Astro или Next.js - например, кастомный статический генератор, устаревшая CMS без плагина конвертации форматов, или ситуация, где нужен полный контроль над CDN-трансформациями изображений (Cloudflare Images, imgix, Cloudinary). В этом случае весь код <picture>, srcset и fetchpriority из предыдущих разделов пишется вручную или генерируется на этапе сборки собственным скриптом.

Для проектов, которые уезжают со старого стека, где такой автоматизации не было в принципе, разбор архитектуры и техник переноса собран в материале про миграцию сайта на современный стек - при переезде на Astro или Next.js оптимизация изображений перестаёт быть ручной задачей на каждую новую картинку.

Реальный кейс: изображения как единственная причина плохого LCP

В типовом проекте оптимизации маркетингового сайта на Next.js LCP держался на уровне 4.1 секунды при полностью зелёном аудите по остальным пунктам - минифицированный JS, настроенный CDN, отсутствие блокирующих скриптов.

Причина оказалась исключительно в hero-изображении: JPEG весом 1.4 МБ в разрешении 2400x1260px, без srcset, без fetchpriority, с loading="lazy", унаследованным от глобальной настройки компонента карточки.

Что изменили:

  1. Сконвертировали hero-изображение в AVIF с WebP fallback через <picture> - вес основного варианта упал с 1.4 МБ до 240 КБ
  2. Добавили srcset с четырьмя вариантами по ширине (480w, 800w, 1200w, 2400w) и корректный sizes
  3. Убрали унаследованный loading="lazy", заменили на fetchpriority="high"
  4. Ничего не меняли в хостинге, CDN или остальном коде страницы

Результат через одну итерацию деплоя, данные CrUX:

  • LCP: 4.1 с -> 1.8 с (из зоны «плохо» в зону «хорошо»)
  • Вес hero-изображения: 1.4 МБ -> 240 КБ (в 5,8 раза меньше)
  • Остальные метрики (CLS, INP) не изменились - подтверждает, что проблема была локализована именно в изображении

Ключевой вывод: почти весь прирост LCP дала работа с одним файлом, без изменений в инфраструктуре или фреймворке.

Для кого актуально

Эта оптимизация особенно критична для компаний, у которых на сайте есть один явный LCP-элемент - маркетинговый лендинг с hero-баннером, страница товара с крупным фото, блог с обложками статей. Если PageSpeed показывает LCP выше 2.5 секунды при в целом здоровом стеке (нет тяжёлого page builder, нет избыточных сторонних скриптов), в 8 случаях из 10 причина - именно в изображении, а не в архитектуре.

Если аудит показывает, что проблема системная - не хватает не только оптимизации конкретной картинки, а всего пайплайна работы с изображениями на сайте, - имеет смысл смотреть на поддержку и постоянную работу с производительностью сайта: мониторинг CrUX, регулярный аудит новых изображений, которые попадают на сайт через CMS.

Часто задаваемые вопросы

Как понять, что именно изображение - причина плохого LCP?

Откройте отчёт PageSpeed Insights или Lighthouse и найдите раздел «Largest Contentful Paint element» - там прямо указан DOM-элемент. Если это <img> или блок с background-image, дальнейшая оптимизация LCP через изображения - изображения решает большую часть проблемы. Если LCP-элемент - это текстовый блок или видео, работа с картинками эффекта не даст, нужно смотреть на шрифты и блокирующие скрипты.

Нужно ли конвертировать в AVIF все изображения на сайте, а не только hero?

Нет. AVIF даёт максимальный эффект именно на LCP-элементе, где вес файла напрямую влияет на метрику ранжирования. Для остальной галереи (карточки, миниатюры, изображения в статьях) WebP - более практичный выбор: сопоставимая экономия трафика при кодировании в 5-10 раз быстрее, что важно при массовой обработке сотен изображений в CMS.

Почему fetchpriority="high" не работает, если изображение уже в WebP?

Формат и приоритет загрузки решают разные задачи. fetchpriority не уменьшает вес файла - он лишь заставляет браузер скачать этот конкретный ресурс раньше остальных. Если сам файл весит 1.5 МБ даже в WebP, приоритет ускорит момент начала загрузки, но не сократит время самой загрузки. Оба инструмента - формат и приоритет - работают вместе, а не заменяют друг друга.

Что делать, если CMS автоматически проставляет loading=“lazy” всем изображениям?

Большинство современных CMS и билдеров позволяют переопределить это поведение точечно для конкретного изображения - через параметр компонента, кастомное поле или ручную правку шаблона hero-блока. Если такой возможности нет на уровне темы, loading="lazy" можно принудительно убрать через JavaScript после рендера, но это менее надёжно, чем корректная разметка на этапе сервера - предпочтительнее использовать компонент, который явно поддерживает fetchpriority и eager-загрузку.

Сколько времени занимает оптимизация LCP через изображения на реальном сайте?

Если проблема локализована в одном hero-изображении (частый случай), правки занимают несколько часов: конвертация в AVIF/WebP, настройка srcset и sizes, добавление fetchpriority. Изменения в CrUX видны примерно через 28 дней - данные обновляются на основе скользящего окна. Если проблема системная - весь пайплайн загрузки изображений в CMS не генерирует нужные форматы и размеры - потребуется настройка автоматизации на уровне сборки или CDN, это уже задача на 1-2 недели.


Если PageSpeed показывает LCP выше 2.5 секунды, а остальной стек выглядит здоровым - опишите задачу команде Exceltic.dev. Проверим, где именно теряется время на конкретном hero-изображении, и предложим план: от точечной правки формата и srcset до автоматизации через Image-компоненты Astro или Next.js.

Ещё статьи

Все →