Core Web Vitals — метрики Google для реального користувацького досвіду: як швидко з'являється основний контент, як швидко сторінка реагує на дії і наскільки стабільний макет. Вони використовуються системами ранжування Google і, що важливіше, корелюють із конверсією: магазин, що повільно реагує на натискання «Додати в кошик», втрачає клієнтів незалежно від SEO. З березня 2024 року метрика чуйності — Interaction to Next Paint (INP), і саме з нею найчастіше мають проблеми сайти. У гайді пояснюємо три метрики, як знаходити проблеми за польовими даними і які виправлення працюють у застосунках на React і Next.js.

Три Core Web Vitals

Кожна метрика оцінюється за 75-м перцентилем завантажень сторінки, окремо для мобільних і десктопу (web.dev: Web Vitals):

Метрика Що вимірює Добре Погано
LCP — Largest Contentful Paint Коли рендериться найбільше зображення чи текстовий блок ≤ 2,5 с > 4 с
INP — Interaction to Next Paint Затримка від дії користувача до наступного кадру, по всіх взаємодіях ≤ 200 мс > 500 мс
CLS — Cumulative Layout Shift Неочікувані зсуви видимого контенту ≤ 0,1 > 0,25

Документація Google зазначає, що Core Web Vitals використовуються її системами ранжування, і рекомендує досягати хороших значень і для успіху в пошуку, і для якісного досвіду загалом (Google Search Central: Core Web Vitals). Це не чарівний перемикач ранжування — релевантність усе одно важить найбільше, — але між порівнянними сторінками досвід має значення.

Чому INP замінив FID

First Input Delay вимірював лише затримку перед початком обробки першої взаємодії. Більшість сайтів легко його проходили, хоча відчувалися млявими. INP став Core Web Vital 12 березня 2024 року (web.dev: INP is a Core Web Vital). Він спостерігає за всіма кліками, дотиками й натисканнями клавіш протягом візиту і показує одну з найповільніших взаємодій, вимірюючи повний шлях (web.dev: INP):

  1. Input delay — очікування, поки звільниться головний потік, часто через довгі задачі.
  2. Processing duration — виконання ваших обробників подій.
  3. Presentation delay — рендеринг і відмальовування наступного кадру.

Повільний INP може походити з будь-якої з трьох фаз, тож виправлення залежить від того, куди йде час.

Крок 1: Користуйтеся польовими даними, а не лише Lighthouse

Стандартний аудит Lighthouse завантажує сторінку на одному симульованому пристрої без взаємодії з нею, тож виміряти INP не може; Total Blocking Time — лише груба лабораторна заміна. Використовуйте дані реальних користувачів:

  • Звіт Core Web Vitals у Search Console групує URL зі схожими проблемами.
  • PageSpeed Insights показує дані Chrome User Experience Report (CrUX) для URL чи домену (PageSpeed Insights; CrUX).
  • Власний RUM. Бібліотека web-vitals збирає метрики ваших користувачів з атрибуцією, що показує, який елемент і яка фаза спричинили повільну взаємодію (web-vitals на GitHub).
import { onINP, onLCP, onCLS } from "web-vitals/attribution"

function send(metric: { name: string; value: number; attribution?: unknown }) {
  navigator.sendBeacon("/api/vitals", JSON.stringify(metric))
}

onINP((m) => send({ name: m.name, value: m.value, attribution: {
  target: m.attribution.interactionTarget,       // на який елемент натиснули
  inputDelay: m.attribution.inputDelay,
  processing: m.attribution.processingDuration,
  presentation: m.attribution.presentationDelay,
}}))
onLCP(send)
onCLS(send)

З атрибуцією «INP — 380 мс» перетворюється на «випадаючий фільтр на сторінках категорій витрачає 300 мс на обробку» — і це вже можна виправити.

Крок 2: Виправте INP

Розбивайте довгі задачі

Будь-яка задача довша за 50 мс блокує ввід. Діліть роботу і поступайтеся головним потоком, щоб браузер міг реагувати в проміжках (web.dev: оптимізація довгих задач). Для цього призначений API scheduler.yield() з резервним варіантом там, де він не підтримується (MDN: Scheduler.yield):

async function yieldToMain() {
  if ("scheduler" in globalThis && "yield" in (globalThis as any).scheduler) {
    return (globalThis as any).scheduler.yield()
  }
  return new Promise((resolve) => setTimeout(resolve, 0))
}

async function applyFilters(items: Item[]) {
  updateFilterButtonState()        // спершу видимий відгук
  await yieldToMain()              // даємо браузеру відмалювати кадр
  const result = expensiveFilter(items)
  renderResults(result)
}

Спершу відгук, потім обчислення

Одразу оновіть натиснуту кнопку, покажіть спінер чи оптимістичний стан, а потім виконуйте важку роботу. У React позначайте нетермінові оновлення як transitions, щоб введення тексту й кліки залишалися чуйними, поки перерендерюються результати (React: useTransition).

Менше роботи в обробниках подій

  • Застосовуйте debounce до пошуку за введенням і не перераховуйте похідні дані на кожне натискання клавіші.
  • Переносьте важкі обчислення — парсинг, сортування великих списків — у Web Worker.
  • Уникайте layout thrashing: не чергуйте в циклі читання макета (offsetHeight) і запис стилів.

Зменшуйте вартість рендерингу

  • Великий DOM робить кожне оновлення дорогим. Віртуалізуйте довгі списки, пагінуйте таблиці.
  • Використовуйте content-visibility: auto для секцій за межами екрана.
  • Перевірте сторонні скрипти — чат-віджети, менеджери тегів, інструменти A/B-тестування часто псують INP. Завантажуйте їх пізніше або приберіть невикористовувані.

React Compiler, стабільний у Next.js 16, автоматично прибирає багато зайвих перерендерів; див. Next.js 16 у продакшені.

Крок 3: Виправте LCP

Розкладіть LCP на час до першого байта, затримку завантаження ресурсу, час його завантаження і затримку рендерингу (web.dev: LCP). Звичні виправлення:

  • Швидко віддавайте HTML. Статична генерація чи часткове пререндерування, кешування на CDN і швидкий бекенд.
  • Робіть LCP-зображення видимим якомога раніше. Використовуйте справжній <img> у початковому HTML, а не CSS-фон чи компонент, що рендериться на клієнті; додайте fetchpriority="high" і ніколи не вмикайте lazy-loading для головного зображення. У Next.js це робить next/image з priority.
  • Підбирайте розмір зображень і використовуйте сучасні формати AVIF чи WebP.
  • Уникайте ресурсів, що блокують рендеринг: вбудовуйте критичний CSS, відкладайте некритичний JavaScript, preload ключових шрифтів.

Крок 4: Виправте CLS

Зсуви макета спричиняє контент, що змінює розмір після рендерингу (web.dev: CLS):

  • Завжди задавайте width і height (або aspect-ratio) для зображень, відео й iframe.
  • Резервуйте місце для реклами, вбудованого контенту і банерів cookies.
  • Завантажуйте вебшрифти з font-display: optional або з підібраними метриками резервного шрифту, щоб текст не перекомпоновувався.
  • Ніколи не вставляйте контент над наявним, якщо це не відповідь на дію користувача.

Процес, що тримає метрики «зеленими»

  1. Збирайте RUM-дані з атрибуцією для кожного шаблону сторінок.
  2. Встановіть бюджети: INP до 200 мс, LCP до 2,5 с, CLS до 0,1 на p75 для мобільних.
  3. Щотижня переглядайте найгірші шаблони й виправляйте головного порушника.
  4. Додайте перевірку Lighthouse CI на регресії LCP і CLS у pull request'ах; польових даних INP вона не замінить, але очевидні проблеми зловить рано.
  5. Відстежуйте метрики разом із бізнес-показниками — конверсією, відмовами, додаваннями в кошик.

Продуктивність — лише частина технічного SEO; решта — метадані, sitemap, hreflang і структуровані дані — у статті Технічне SEO для Next.js. Якщо плануєте зробити сайт таким, що встановлюється, порівняйте варіанти у статті PWA чи нативний застосунок.

Типові причини поганого INP у застосунках на React і Next.js

  • Великі клієнтські компоненти, що перерендерюються на кожне натискання клавіші, — наприклад, пошукове поле, яке синхронно фільтрує список із тисяч елементів.
  • Гідратація великих сторінок, що блокує головний потік саме тоді, коли користувач починає натискати; переносьте статичні частини в Server Components.
  • Важка аналітика й менеджери тегів, що запускають синхронну роботу на кожен клік.
  • Бібліотеки стану, що оновлюють усе дерево через дрібну зміну; розділяйте стан і мемоїзуйте або доручіть це React Compiler.
  • Дорога валідація в onChange у формах; валідуйте на blur або з debounce.

FAQ

Чи впливають Core Web Vitals безпосередньо на ранжування? Google використовує їх у системах ранжування, але релевантність і якість контенту важать більше. Хороші метрики найбільше допомагають, коли конкурентні сторінки схожі.

Чому Lighthouse показує 95, а INP поганий? Стандартний аудит Lighthouse не взаємодіє зі сторінкою, тож не може виміряти INP. Джерело правди — польові дані реальних користувачів.

Які пристрої найважливіші? Мобільні, особливо Android-смартфони середнього класу, де обмеження CPU роблять довгі задачі значно довшими, ніж на ноутбуці розробника.

Коли покращення з'являться в Search Console? Дані CrUX охоплюють попередні 28 днів, тож очікуйте кілька тижнів, поки звіти відобразять виправлення.

Джерела

  1. web.dev. Web Vitals, Interaction to Next Paint, LCP, CLS.
  2. web.dev (2024). Interaction to Next Paint becomes a Core Web Vital on March 12.
  3. web.dev. Optimize long tasks і Optimize INP.
  4. Google Search Central. Understanding Core Web Vitals and Google search results.
  5. Google Chrome. Бібліотека web-vitals і Chrome UX Report.
  6. MDN. Scheduler.yield().
  7. React. useTransition.