Core Web Vitals на практике: LCP, CLS и INP без иллюзий
Google требует зелёные метрики на уровне 75-го перцентиля реальных пользователей. Разбираем, что чинить в LCP, CLS и INP и почему лабораторный тест иногда врёт.

Вы открыли PageSpeed Insights, увидели зелёные 95 баллов в лабораторном тесте и красную плашку «Не пройдена проверка Core Web Vitals» — и не поняли, как одно сочетается с другим. Так бывает часто: лабораторный тест (Lighthouse) снимает метрики с одного прогона на эмулированном устройстве, а Google для ранжирования смотрит поле — реальные визиты за 28 дней на уровне 75-го перцентиля. Это значит: если у четверти ваших пользователей страница тормозит, метрика красная, даже когда у остальных всё летает.
Разбираем три метрики Core Web Vitals по состоянию на 2024–2025 год: LCP (отрисовка крупнейшего элемента), CLS (сдвиги вёрстки) и INP (задержка отклика на действие). INP заменил FID в марте 2024 года — если вы всё ещё оптимизируете под FID, вы чините метрику, которой больше нет.
Пороги, к которым идём
Границы «хорошо / нужно улучшить / плохо» задаёт сам Google. Оптимизируют не среднее, а 75-й перцентиль: три четверти визитов должны укладываться в «хорошо».
| Метрика | Что измеряет | Хорошо | Плохо (свыше) |
|---|---|---|---|
| LCP | Время до отрисовки крупнейшего элемента | ≤ 2,5 с | 4,0 с |
| CLS | Сумма неожиданных сдвигов вёрстки | ≤ 0,1 | 0,25 |
| INP | Задержка отклика на взаимодействие | ≤ 200 мс | 500 мс |
Точные значения порогов и методику подсчёта сверяйте на web.dev — Google их периодически пересматривает.
Зелёный Lighthouse на вашем MacBook ничего не говорит о телефоне пользователя за 12 тысяч рублей на 4G. Ранжирование считается по полю, а не по лабе.
LCP: сначала найдите сам элемент
LCP — это момент, когда браузер дорисовал самый крупный видимый элемент в области просмотра. Обычно это главная картинка, видео-постер или большой блок текста. Первая ошибка — оптимизировать не тот элемент. Откройте DevTools → вкладка Performance, запишите загрузку, найдите маркер LCP на таймлайне: он подсветит конкретный узел DOM.
Типичный расклад по времени LCP делится на четыре части: время ответа сервера (TTFB), задержка загрузки ресурса, время самой загрузки и задержка отрисовки. Смотреть надо, какая часть съедает больше.
Что реально двигает LCP
- Ресурс грузится поздно. Если LCP — картинка, добавьте ей
fetchpriority="high"и уберите с неёloading="lazy". Ленивая загрузка на элементе первого экрана прямо вредит LCP — это частая ошибка после «оптимизации всех картинок скопом». - Ресурс поздно обнаружен. Если картинка подставляется через CSS
background-imageили JS, браузер узнаёт о ней слишком поздно. Добавьте<link rel="preload" as="image" href="...">в<head>. - Шрифт блокирует текст. Если LCP — текстовый блок, добавьте шрифту
font-display: swapиpreloadдля файла шрифта. - Медленный TTFB. Ниже TTFB 600 мс никакая клиентская оптимизация не спасёт. Кешируйте ответ, включите CDN, уберите тяжёлые запросы к БД из критического пути.
<head>
<!-- предзагрузка LCP-картинки: браузер начнёт качать её сразу -->
<link rel="preload" as="image"
href="/img/hero.webp"
fetchpriority="high">
</head>
<!-- сам элемент: без lazy, с высоким приоритетом -->
<img src="/img/hero.webp"
width="1200" height="600"
fetchpriority="high"
alt="...">Проверка результата: перезапишите Performance-профиль, маркер LCP должен сместиться влево. Через сутки-двое посмотрите поле в PageSpeed Insights — лаба покажет эффект сразу, поле подтянется за 28 дней.
CLS: почти всегда виноваты размеры
CLS набирается, когда элементы прыгают после того, как пользователь уже увидел страницу. Загрузилась картинка без заданных размеров — текст под ней подпрыгнул. Подгрузился баннер сверху — вся страница уехала вниз ровно в момент, когда палец шёл к кнопке. Метрика считает произведение доли сдвинутой площади на дистанцию сдвига и суммирует такие события в сессионное окно.
Четыре источника сдвигов
- Картинки и видео без размеров. Всегда задавайте
widthиheightв атрибутах (илиaspect-ratioв CSS). Браузер зарезервирует место до загрузки. - Реклама и встраиваемые блоки. Задайте контейнеру фиксированную минимальную высоту под слот, чтобы объявление не расталкивало контент.
- Шрифты. Подмена системного шрифта на веб-шрифт меняет метрики строки — текст перекомпоновывается. Помогает
size-adjustи предзагрузка шрифта. - Контент, вставляемый через JS. Баннер cookie, уведомление, «шапка появилась при скролле» — если это двигает соседей, а не лежит поверх (
position: fixed/absolute), вы копите CLS.
/* Резервируем пропорции до загрузки картинки */
img {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}
/* Слот под рекламу не должен схлопываться */
.ad-slot {
min-height: 250px;
}Как найти виновника: в DevTools → Performance включите Layout Shift Regions либо на панели Rendering отметьте Layout Shift Regions — сдвиги подсветятся синим прямо на странице во время взаимодействия.
INP: метрика, которую многие ещё не поняли
INP заменил FID и меряет не первую задержку, а отклик на все взаимодействия за визит — берётся практически худшее. Взаимодействие делится на три фазы: задержка ввода (пока занят главный поток), обработка (ваши обработчики событий) и задержка отрисовки следующего кадра. INP красный почти всегда из-за длинных задач в JavaScript, которые блокируют главный поток.
Классический сценарий: пользователь жмёт кнопку, а в это время выполняется тяжёлый обработчик на 300 мс — сортировка большого массива, синхронный расчёт, обход DOM. Пока он крутится, браузер не может ни обработать клик, ни отрисовать реакцию. Пользователь видит фриз.
Что помогает
- Разбивайте длинные задачи. Задача дольше 50 мс уже блокирует отклик. Выносите тяжёлые вычисления в
Web Workerили дробите с уступкой главному потоку. - Уступайте потоку. В браузерах, где доступен
scheduler.yield(), используйте его между кусками работы; проверьте поддержку в вашей аудитории, это относительно новый API. Запасной вариант — разбить работу черезsetTimeoutс нулевой задержкой. - Обновляйте UI немедленно, тяжёлое — потом. Сначала покажите визуальный отклик (спиннер, изменение состояния кнопки), затем в следующем кадре запускайте расчёт.
- Уберите лишние обработчики. Много слушателей на одном событии, дорогие ре-рендеры на каждый ввод — частая причина у SPA.
// Плохо: клик блокирует поток на всё время расчёта
button.addEventListener('click', () => {
const result = heavyCompute(bigArray); // 300 мс фриза
render(result);
});
// Лучше: сразу даём отклик, тяжёлое — после отрисовки
button.addEventListener('click', () => {
button.classList.add('is-loading'); // мгновенная реакция
// отдаём кадр на отрисовку, потом считаем
requestAnimationFrame(() => {
setTimeout(() => {
const result = heavyCompute(bigArray);
render(result);
button.classList.remove('is-loading');
}, 0);
});
});INP правильно замерять только в поле — в Lighthouse его нет, потому что лаба не воспроизводит реальные клики пользователя. Собирайте через библиотеку web-vitals от Google или через отчёт CrUX в Search Console.
Чем мерить: лаба и поле — разные инструменты
| Инструмент | Тип данных | Для чего |
|---|---|---|
| Lighthouse / DevTools | Лаба (один прогон) | Быстро проверить гипотезу, найти LCP-элемент |
| PageSpeed Insights | Лаба + поле (CrUX) | Сверить лабу с реальными пользователями |
| Search Console → Core Web Vitals | Поле (28 дней) | Что видит Google для ранжирования |
| web-vitals + свой сбор | Поле (ваша аналитика) | INP и разбивка по страницам/устройствам |
Что делать дальше
Начните с поля, а не с лабы: откройте Search Console и посмотрите, какая из трёх метрик красная и на каком типе страниц. Чините её первой — остальные две могут быть в норме, и трогать их нет смысла. Поставьте сбор web-vitals на боевой сайт, чтобы видеть INP по реальным визитам и по устройствам: разница между десктопом и дешёвым Android бывает кратной. После каждого изменения ждите обновления поля — CrUX считает скользящее окно 28 дней, и мгновенного скачка в Search Console не будет, даже если лаба позеленела сразу.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Core Web Vitals — web.dev, web-vitals library — GitHub
Частые вопросы
У меня Lighthouse показывает 95 баллов, а Google пишет, что Core Web Vitals не пройдены. Почему?
Я оптимизировал под FID, а он теперь ничего не значит?
Изменил код, лаба позеленела, а в Search Console по-прежнему красный. Что не так?
Достаточно ли поставить lazy-load на все картинки для скорости?
Как измерить INP, если в Lighthouse его нет?
Нужны ли эти метрики, если сайт и так неплохо ранжируется?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.