Клавиша / esc

Как измерять и улучшать Core Web Vitals

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

Время чтения: 10 мин

Core Web Vitals мало просто измерить — важно понимать, где смотреть эти данные, чем лабораторные результаты отличаются от полевых и как искать причину плохой метрики, а не только видеть, что она плохая. Разберём инструменты, подход к диагностике и типичные ошибки при работе с LCP, INP и CLS.

Лабораторные данные !== реальные

Скопировано

Это самая частая причина непонимания: «Почему метрика в Lighthouse зелёная, а в Search Console красная?» Есть два принципиально разных источника данных:

  • лабораторные (lab) — синтетический прогон в контролируемых условиях: фиксированное устройство, заданная сеть, без реального пользователя. Это Lighthouse и симуляция в PageSpeed Insights;
  • реальные или полевые (field) — данные реальных пользователей (RUM, Real User Monitoring). Это CrUX (Chrome User Experience Report), который Google учитывает в ранжировании, и своя аналитика на библиотеке web-vitals — она в ранжировании не участвует, но даёт более детальную картину по вашей аудитории. Оба источника показывают 75-й перцентиль распределения (значение, хуже которого метрика бывает только у четверти загрузок).

Данные собираются скользящим окном в 28 дней. Поэтому после релиза с оптимизациями не ждите, что зелёная зона в Search Console наступит завтра: эффект проявляется постепенно и полностью виден только примерно через 28 дней.

Почему лабораторные и полевые данные расходятся:

  • INP в лабораторных условиях не измеряется вообще — он зависит от взаимодействий пользователя на протяжении всей жизни страницы в реальный момент, а синтетика не знает, когда и куда нажмёт человек. Lighthouse вместо INP показывает приближение, TBT (Total Blocking Time), и это лишь приблизительный ориентир;
  • CLS различается — в лаборатории фиксируется в основном сдвиг при загрузке (верх страницы), а в поле — за весь жизненный цикл: пользователь скроллит, догружаются ленивые картинки без размеров, срабатывают A/B-тесты, и все эти сдвиги лабораторные измерители не видят;
  • LCP различается — разным людям на разных экранах виден разный LCP-элемент; у реальных пользователей часть ресурсов уже находится в кеше браузера, а в синтетике кеш холодный.

Вывод: для отладки удобны лабораторные инструменты (они воспроизводимы и подсказывают, что чинить), а для приоритизации и оценки результата доверяйте полевым данным.

Инструменты

Скопировано

Лабораторные инструменты (Lab Data)

Скопировано

Инструменты, которые запускают контролируемые тесты и помогают найти конкретные проблемы в коде, загрузке и рендеринге страницы:

  • Lighthouse (в DevTools, CLI или CI) — лабораторный аудит с конкретными рекомендациями. Помните про оговорку с INP: в лабораторных тестах Lighthouse использует TBT (Total Blocking Time) как приближение, а реальный INP измеряется только на реальных пользователях. Lighthouse CI удобно ставить в пайплайн, чтобы ловить регрессии до релиза;
  • Chrome DevTools — основной инструмент глубокой диагностики. На вкладке Performance — профилирование главного потока, анализ долгих JavaScript-задач, Layout Shifts для CLS и причин медленных взаимодействий. Для INP включите Screenshots, запишите трейс во время взаимодействия и смотрите дорожку Interactions: наведение на конкретное взаимодействие покажет три отрезка — input delay, processing, presentation delay — с подписью длительности каждого, а красные треугольники в стеке вызовов подсветят виновные обработчики. Если проблема не воспроизводится локально, включите CPU throttling (4× или 6×) или, как вариант, подключите реальный Android-телефон через remote debugging. На самой панели Performance есть боковая вкладка Insights с упрощённым разбором ключевых проблем, а в разделе Application → Back/forward cache можно проверить, может ли страница использовать bfcache (back/forward cache, кеш, из которого браузер мгновенно восстанавливает страницу целиком при переходе «назад»/«вперёд», без новой загрузки);
  • PageSpeed Insights — удобная отправная точка для диагностики: показывает лабораторный прогон Lighthouse и рекомендации по оптимизации.

Полевые инструменты (Field Data / Real User Monitoring)

Скопировано

Инструменты, которые показывают реальный пользовательский опыт на основе фактических загрузок и взаимодействий:

  • PageSpeed Insights кроме Lighthouse на сервере показывает данные CrUX за последние 28 дней, если по сайту достаточно трафика;
  • Search Console → Core Web Vitals — полевые данные по группам URL, которые показывают реальное качество страниц с точки зрения пользователей, и на них Google смотрит, когда оценивает опыт взаимодействия;
  • CrUX Vis и CrUX Dashboard / BigQuery — публичные исторические данные CrUX по домену, по ним видно тренды Core Web Vitals;
  • сторонние RUM-сервисы — DebugBear, SpeedCurve, Vercel Speed Insights, RUMVision. В отличие от CrUX, они обновляются быстрее, работают не только с Chrome и дают более детальную атрибуцию проблем: какой ресурс, скрипт или компонент повлиял на метрику;
  • библиотека web-vitals собирает Core Web Vitals у реальных пользователей и отправляет данные в вашу аналитику.

Своя аналитика на web-vitals

Скопировано

На практике полезно отправлять не только саму метрику, но и дополнительный контекст: URL страницы, тип устройства, размер экрана, версию приложения или номер релиза. Тогда после выкладки новой версии будет гораздо проще понять, какой именно релиз ухудшил Core Web Vitals и для каких пользователей.

        
          
          import { onCLS, onINP, onLCP } from "web-vitals";function sendToAnalytics(metric) {  const body = JSON.stringify({    name: metric.name,    value: metric.value,    id: metric.id,    url: location.href,    release: window.APP_RELEASE,  });  navigator.sendBeacon("/analytics", body);}onCLS(sendToAnalytics);onINP(sendToAnalytics);onLCP(sendToAnalytics);
          import { onCLS, onINP, onLCP } from "web-vitals";

function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    id: metric.id,
    url: location.href,
    release: window.APP_RELEASE,
  });
  navigator.sendBeacon("/analytics", body);
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);

        
        
          
        
      

В Next.js те же метрики у реальных пользователей собирает useReportWebVitals. Вынесите хук в отдельный клиентский компонент, чтобы не добавлять 'use client' всему layout:

        
          
          "use client";import { useReportWebVitals } from "next/web-vitals";const report = (metric) =>  navigator.sendBeacon("/analytics", JSON.stringify(metric));export function WebVitals() {  useReportWebVitals(report);  return null;}
          "use client";

import { useReportWebVitals } from "next/web-vitals";

const report = (metric) =>
  navigator.sendBeacon("/analytics", JSON.stringify(metric));

export function WebVitals() {
  useReportWebVitals(report);
  return null;
}

        
        
          
        
      

У библиотеки есть attribution-сборка, которая показывает, что именно виновато: для CLS — селектор самого двигающегося элемента, для INP — тип взаимодействия и разбивка по фазам, для LCP — какой элемент и из чего сложилось его время.

        
          
          import { onLCP } from "web-vitals/attribution";onLCP((metric) => {  // metric.attribution.element, .url, .timeToFirstByte,  // .resourceLoadDelay, .resourceLoadDuration, .elementRenderDelay  console.log(metric.attribution);});
          import { onLCP } from "web-vitals/attribution";

onLCP((metric) => {
  // metric.attribution.element, .url, .timeToFirstByte,
  // .resourceLoadDelay, .resourceLoadDuration, .elementRenderDelay
  console.log(metric.attribution);
});

        
        
          
        
      

Для INP attribution-сборка идёт дальше и отдаёт interactionTarget (какой элемент), interactionType (клик/тап/клавиша) и, самое ценное, массив longAnimationFrameEntries из LoAF API (Long Animation Frames): там для каждого «тяжёлого» кадра расписано, какой именно скрипт его вызвал — sourceURL, sourceFunctionName, даже позиция символа в файле. Это разница между «у нас плохой INP» и «вот эта функция в vendor-bundle.js виновата».

Если строите свою аналитику на web-vitals, учтите две детали.

Во-первых, отправляйте метрики при событии visibilitychange (когда document.visibilityState становится hidden), а не по beforeunload/unload. visibilitychange — последний надёжный момент, когда ещё можно отправить данные через navigator.sendBeacon() или fetch(..., { keepalive: true }), особенно на мобильных устройствах. Кроме того, устаревшие события beforeunload и unload могут помешать работе bfcache.

Во-вторых, считайте перцентили, а не среднее: одно экстремальное значение на медленном устройстве может сильно исказить среднее, но почти не повлияет на 75-й перцентиль.

Подход к работе с метриками

Скопировано
  1. сначала измерьте на реальных пользователях (CrUX / web-vitals): поймите, какая из трёх Core-метрик в красной или жёлтой зоне для десктопа и мобильных устройств;
  2. воспроизведите в лаборатории (Lighthouse, DevTools) и найдите конкретного виновника через DevTools или attribution;
  3. чините по разбивке метрики: не «улучшаем LCP вообще», а «у нас увеличен отрезок render delay из-за блокирующего CSS»;
  4. проверьте, что не сломали соседнюю метрику: например, агрессивный preload всего подряд может ухудшить LCP, потому что слишком большое количество предзагруженных ресурсов начинает конкурировать с действительно критичными ресурсами за пропускную способность сети и приоритеты загрузки браузера;
  5. мониторьте постоянно. Производительность деградирует незаметно: добавили виджет, обновили зависимость — и метрика испортилась. Держите web-vitals в продакшене и следите за трендом.

Оптимизацию почти никогда не делают за один проход. После каждого изменения перепроверяйте все Core Web Vitals — улучшение одной метрики может случайно ухудшить другую.

Как связать метрику с пользовательским сценарием

Скопировано

Удобно начинать не с технического термина, а с того, что замечает человек:

  • долго не появляется главное содержимое — проверяем LCP;
  • интерфейс медленно отвечает на нажатия — проверяем INP;
  • элементы меняют положение во время загрузки — проверяем CLS;
  • страница долго остаётся полностью пустой — проверяем FCP и TTFB.

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

На практике почти всегда начинайте с мобильных устройств. Именно мобильные данные чаще всего оказываются в жёлтой или красной зоне из-за менее производительных процессоров и более медленных сетей.

Шаблон постановки задачи

Скопировано

Хорошая постановка задачи может выглядеть так:

На мобильных устройствах у карточек товаров LCP 4,6 с на 75-м перцентиле. Проблема затрагивает 38% органического трафика. Цель — довести LCP до 2,5 с или ниже и проследить изменение доли отказов.

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

Короткие варианты для других метрик:

  • INP: на мобильной странице оформления заказа кнопка «Продолжить» отвечает до 700 мс; цель — не более 200 мс;
  • CLS: блок рекомендаций сдвигает текст, CLS составляет 0,31; цель — зарезервировать место и снизить показатель до 0,1.

Частые ошибки, на которых легко обжечься

Скопировано

Кроме уже разобранных выше ловушек — лабораторные баллы вместо полевых значений, ожидание мгновенного эффекта, оптимизация «среднего» вместо перцентиля и порча соседней метрики агрессивным preload, — есть ещё одна: оптимизировать страницы с минимальным трафиком. Начинайте с тех, что получают основную долю посещений или напрямую влияют на бизнес — именно они дадут наибольший эффект для пользователей и отчётов Core Web Vitals.