Что такое LCP
СкопированоLCP (Largest Contentful Paint) измеряет время от начала загрузки страницы до момента, когда в области просмотра (viewport) был отрисован самый большой элемент контента. Обычно это главное изображение (hero image), обложка статьи, большой текстовый блок или постер видео. LCP-элемент нельзя назначить вручную — браузер сам определяет его во время загрузки страницы, а проверить, какой именно элемент он выбрал, можно в PageSpeed Insights или на вкладке Performance в DevTools.
Хорошим считается LCP до 2,5 секунды, плохим — больше 4 секунд.
Пока главный контент не появился, пользователь не понимает, туда ли он попал и может ли взаимодействовать со страницей. Плохой LCP повышает риск раннего ухода со страницы и сокращает число людей, которые доходят до целевого действия.
Пример сценария: пользователь открывает страницу товара, но его крупное изображение появляется только через пять секунд. Не увидев основной контент, человек может решить, что страница работает слишком медленно, и вернуться к результатам поиска.
Из чего складывается LCP
СкопированоЧтобы улучшить LCP, сначала нужно понять, на каком этапе теряется время. Google выделяет четыре основных этапа и ориентировочную долю времени, которую каждый из них занимает у хорошо оптимизированной страницы — на практике соотношение сильно зависит от конкретного сайта:
- TTFB (ориентировочно ~40% времени) — время от начала перехода на страницу до получения первого байта ответа от сервера, подробнее — в статье про TTFB. Пока браузер не получил первый байт ответа, он не может начать парсить HTML и находить ресурсы, нужные для отображения LCP-элемента;
- задержка загрузки ресурса (обычно до 10%) — время между получением первого байта HTML и началом загрузки ресурса, нужного для отображения LCP-элемента. Например, браузер может поздно обнаружить главное изображение или другой ресурс LCP-элемента;
- длительность загрузки ресурса (ориентировочно ~40%) — время, которое требуется для загрузки ресурса LCP-элемента. На этот этап влияют размер файла, скорость сети, формат ресурса и способ загрузки;
- задержка отрисовки (обычно до 10%) — время между моментом, когда ресурс уже доступен, и фактическим появлением элемента на экране. На этом этапе задержку чаще всего создают блокирующий CSS и JavaScript, длинные задачи (Long Tasks), ожидание загрузки шрифтов и другие операции, откладывающие отрисовку.
Если LCP-элемент не зависит от загрузки отдельного ресурса (например, это крупный текстовый блок), то этапы «задержка загрузки ресурса» и «длительность загрузки ресурса» отсутствуют. В этом случае итоговое значение LCP складывается только из TTFB и задержки отрисовки.
Как улучшить LCP
СкопированоTTFB
СкопированоБолее подробно в статье о TTFB. Тут кратко:
- уберите лишние редиректы в цепочке до страницы;
- оптимизируйте свой веб-сервер;
- пользуйтесь CDN для статических файлов и не забудьте настроить на нём кеш, чтобы контент отдавался с ближайшего к пользователю узла.
Задержка загрузки ресурса
СкопированоЧтобы браузер узнал про LCP-картинку как можно раньше:
- держите LCP-картинку в HTML обычным
<img src>, не подставляйте её с помощью JavaScript и не прячьте вdata. Тогда её найдёт предварительный сканер разметки (preload-сканер). Он вычитывает HTML и начинает качать ресурсы ещё до выполнения скриптов;- src - не используйте
loadingна LCP-картинке: ленивая загрузка откладывает именно то, что должно появиться первым;= "lazy" - поставьте
fetchpriorityна главную картинку — браузер начнёт загружать её с более высоким приоритетом. Не назначайте высокий приоритет более чем 1–2 ресурсам, иначе теряется смысл в оптимизации;= "high" - в Next.js укажите
priorityу LCP-картинки в компоненте<: это отключит ленивую загрузку и выставит запросу высокий приоритет:Image>
import Image from "next/image";import hero from "./hero.png";<Image src={hero} alt="Обложка" priority placeholder="blur" />;
import Image from "next/image";
import hero from "./hero.png";
<Image src={hero} alt="Обложка" priority placeholder="blur" />;
Если LCP-изображение задаётся через CSS background, браузер обнаружит его только после загрузки и разбора CSS. По возможности используйте <img>, а если это невозможно, добавьте <link rel:
<!-- LCP-картинка: в HTML, с высоким приоритетом, без lazy --><img src="hero.avif" alt="Обложка" width="1200" height="630" fetchpriority="high"/><!-- если картинка ссылается только из CSS — предзагрузим её --><link rel="preload" as="image" href="hero.avif" fetchpriority="high" /><!-- для адаптивной предзагрузки — imagesrcset/imagesizes --><link rel="preload" as="image" imagesrcset="hero-800.avif 800w, hero-1600.avif 1600w" imagesizes="100vw" fetchpriority="high"/>
<!-- LCP-картинка: в HTML, с высоким приоритетом, без lazy -->
<img
src="hero.avif"
alt="Обложка"
width="1200"
height="630"
fetchpriority="high"
/>
<!-- если картинка ссылается только из CSS — предзагрузим её -->
<link rel="preload" as="image" href="hero.avif" fetchpriority="high" />
<!-- для адаптивной предзагрузки — imagesrcset/imagesizes -->
<link
rel="preload"
as="image"
imagesrcset="hero-800.avif 800w, hero-1600.avif 1600w"
imagesizes="100vw"
fetchpriority="high"
/>
Если ресурс на другом домене, добавьте <link rel в секцию <head>, а ещё лучше держите его на основном домене.
Длительность загрузки ресурса
СкопированоДля фотореалистичных LCP-изображений — hero-баннеров и обложек — SVG не подходит: используйте растровые форматы AVIF, WebP или JPEG.
Сжимайте и переводите картинки в современные форматы. Порядок предпочтения: AVIF (на ~50% легче JPEG, поддержка ~95%), далее WebP (на 25–35% легче, поддержка ~96%), и JPEG как запасной вариант для браузеров без поддержки современных форматов. Раздавайте через <picture> с несколькими <source>, чтобы браузер сам выбрал, что он поддерживает:
<picture> <!-- Самый предпочтительный формат --> <source srcset="hero.avif" type="image/avif" /> <!-- Если AVIF не поддерживается --> <source srcset="hero.webp" type="image/webp" /> <!-- Запасной вариант --> <img src="hero.jpg" alt="Обложка" width="1200" height="630" fetchpriority="high" /></picture>
<picture>
<!-- Самый предпочтительный формат -->
<source srcset="hero.avif" type="image/avif" />
<!-- Если AVIF не поддерживается -->
<source srcset="hero.webp" type="image/webp" />
<!-- Запасной вариант -->
<img
src="hero.jpg"
alt="Обложка"
width="1200"
height="630"
fetchpriority="high"
/>
</picture>
Отдавайте адаптивные размеры через srcset и sizes: не грузите 4K-картинку на мобильные устройства.
<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" alt="Обложка" width="1280" height="720" /></picture>
<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"
alt="Обложка"
width="1280"
height="720"
/>
</picture>
Используйте CDN и image CDN, которые на лету подбирают формат и размер под устройство пользователя. Современный image CDN автоматически выбирает подходящий формат и размер изображения: смотрит на Client Hints (заголовки, в которых браузер сам сообщает серверу ширину вьюпорта и плотность пикселей экрана), заголовок Save и User-Agent: Chrome получит AVIF/WEBP, старый браузер — PNG/JPEG, без вручную написанных <picture>-развилок. Экономия по весу файла может составить 40–80%.
Настройте долгое кеширование (Cache с большим max) для статических файлов — шрифтов, картинок и т. д. Не забудьте про query-параметры: сортируйте их и убирайте аналитические метки, не влияющие на содержимое страницы, — иначе они ломают кеш. И не отправляйте Set вместе с кешируемыми ответами: большинство CDN не кеширует их вовсе.
Уберите конкуренцию за пропускную способность сети: понизьте fetchpriority второстепенным ресурсам, отложите сторонние скрипты через defer или async. Если на экране несколько картинок, но LCP-элементом станет только одна (например, первый слайд карусели), явно понизьте приоритет остальных через fetchpriority, чтобы они не отнимали пропускную способность у главного элемента.
Совсем маленькие изображения можно вставить как data URL прямо в HTML/CSS (data). Так браузер получает картинку без отдельного сетевого запроса. Годится только для небольших иконок: base64 увеличивает вес файла примерно на треть и не кешируется отдельно от документа, поэтому для LCP-картинок это скорее исключение, чем правило.
Если LCP-элемент — это простая векторная графика (иконка, лого, иллюстрация без фотографий), вставляйте её инлайновым <svg> прямо в HTML, а не через <img src. Тогда браузеру не придётся делать отдельный запрос за SVG-файлом — графика приедет вместе с HTML, что уменьшит задержку до её отображения. Перед отправкой обработайте свой SVG через SVGO (исходники на GitHub): он вычищает метаданные редактора, комментарии, лишние атрибуты и не влияющие на рендер данные, которые остаются после экспорта из графических редакторов. Для разовой оптимизации без установки есть веб-версия SVGOMG.
На практике этот этап редко оказывается узким местом: у большинства сайтов основную часть LCP съедают TTFB и задержки, а не сама загрузка файла. Прежде чем оптимизировать именно длительность загрузки, проверьте по полевым данным (собранным у реальных пользователей через Chrome UX Report (CrUX) или библиотеку web), действительно ли это ваше слабое место.
Задержка отрисовки
СкопированоСократите блокирующий CSS: вынесите критические стили как инлайновые, остальное грузите отложенно. Удалите неиспользуемые правила (проверьте через инструмент Coverage в DevTools). Некритичный файл можно асинхронно загрузить приёмом «preload, который после загрузки превращается в stylesheet». Браузер скачивает файл асинхронно, не блокируя рендер, а onload подключает стили, как только они готовы. <noscript> — запасной вариант для отключённого JS. У этого приёма есть свои компромиссы: страница может на мгновение мигнуть нестилизованным содержимым (FOUC), да и поддерживать такой код сложнее:
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'"/><!-- Запасной вариант для браузеров с отключенным JavaScript --><noscript><link rel="stylesheet" href="non-critical.css" /></noscript>
<link
rel="preload"
href="non-critical.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<!-- Запасной вариант для браузеров с отключенным JavaScript -->
<noscript><link rel="stylesheet" href="non-critical.css" /></noscript>
Старайтесь не добавлять синхронный JavaScript в <head>: переносите в конец <body> страницы или добавляйте async или defer по возможности.
Рендерите на сервере (SSR) или используйте статическую генерацию (SSG), чтобы браузер получил LCP-контент сразу в HTML, а не строил его после выполнения JavaScript.
Следите, чтобы тяжёлые задачи на главном потоке не блокировали отрисовку сразу после загрузки картинки (смотрите про длинные задачи в статье про INP).
Не задерживайте отображение текста веб-шрифтами. Если LCP-элемент — текст, используйте font, чтобы браузер сразу показал текст запасным шрифтом и подменил его на кастомный после загрузки, и предзагружайте критические шрифты через <link rel. Иначе браузер может отложить отображение текста до завершения загрузки шрифта.
Углублённая оптимизация
СкопированоЕсли у вас многостраничный сайт (не одностраничное SPA-приложение), присмотритесь к Speculation Rules API. Он позволяет заранее предзагружать или полностью пререндерить наиболее вероятную следующую страницу, например, при наведении курсора на ссылку или на основе заранее заданных правил. При переходе пользователь практически мгновенно видит готовую страницу, а значение LCP для такой навигации становится очень маленьким, сравнимым с восстановлением страницы из bfcache — кеша, из которого браузер мгновенно достаёт уже отрисованную страницу целиком при переходе «назад»/«вперёд», без новой загрузки.
На момент написания статьи технология поддерживается только браузерами на базе Chromium (Chrome, Edge и другими) и пока недоступна в Safari и Firefox. Но следить за ней уже стоит, особенно если значительная часть вашей аудитории использует Chromium-браузеры.
<script type="speculationrules"> { "prerender": [ { "where": { "href_matches": "/*" }, "eagerness": "moderate" } ] }</script>
<script type="speculationrules">
{
"prerender": [
{ "where": { "href_matches": "/*" }, "eagerness": "moderate" }
]
}
</script>
eagerness задаёт, насколько рано браузер начнёт пререндерить страницу: immediate — сразу для всех подходящих ссылок, eager — как только пользователь навёл курсор, moderate — после недолгого наведения на ссылку, conservative — только когда пользователь начал клик. Чем раньше старт, тем выше шанс, что пререндер окажется зря потраченным, если пользователь передумает.