Оптимізація INP у 2026 році: як зробити сайт по-справжньому чуйним

43% сайтів провалюють INP у 2026 році. У цьому гіді — покрокова діагностика через LoAF API та web-vitals, практичні техніки: scheduler.yield(), Web Workers, фреймворк-специфічні підходи для React, Vue та Angular, і повний чекліст.

Вступ — INP: метрика, яку найчастіше провалюють

У березні 2024 року Google офіційно замінив First Input Delay (FID) на Interaction to Next Paint (INP) як основну метрику чуйності серед Core Web Vitals. І ось що цікаво: FID проходила переважна більшість сайтів, а INP у 2026 році провалюють 43% сайтів, які не вкладаються в поріг 200 мс. Тобто майже кожен другий сайт. Це робить INP найбільш проблемною метрикою серед усіх Core Web Vitals — навіть гірше за LCP.

Чому ж різниця така разюча? FID вимірював затримку лише першої взаємодії зі сторінкою — як правило, це був клік по елементу навігації, коли сторінка ще не встигла завантажити всі скрипти. INP натомість оцінює кожну взаємодію протягом усього сеансу: кліки, натискання клавіш, тапи — і звітує найгірший результат на 75-му перцентилі. Принципово інший рівень вимог.

Для бізнесу цифри промовисті. Сайти, які проходять усі три метрики Core Web Vitals, демонструють на 24% нижчий показник відмов (bounce rate). Інтернет-магазини повідомляють про зростання конверсій на 15–30% після оптимізації INP. А компанія RedBus зафіксувала збільшення продажів на 7% — просто від покращення чуйності свого сайту. Це не абстрактна технічна метрика, а прямий вплив на гроші.

Отже, давайте розберемося, що таке INP, як його діагностувати, які типові причини поганих показників, і головне — як конкретними техніками зробити ваш сайт по-справжньому чуйним у 2026 році.

Що таке INP і чому він важливий

Визначення INP

Interaction to Next Paint (INP) — метрика, яка вимірює загальну чуйність сторінки на взаємодії користувача. На відміну від FID, який фіксував лише затримку введення першої взаємодії, INP охоплює весь життєвий цикл кожної взаємодії — від моменту кліку чи натискання клавіші до моменту, коли браузер відображає наступний кадр із візуальним результатом.

Значення INP для сторінки — це найгірша взаємодія (або близька до найгіршої, якщо взаємодій багато), виміряна на 75-му перцентилі серед усіх відвідувачів.

Три фази INP

Кожна взаємодія складається з трьох послідовних фаз, і загальний час INP — їхня сума:

  1. Input Delay (затримка введення) — час від моменту взаємодії користувача до початку виконання обробників подій. Ця затримка виникає, коли головний потік зайнятий іншою роботою (JavaScript, парсинг тощо) і не може негайно почати обробку події.
  2. Processing Time (час обробки) — час на виконання обробників подій (event handlers). Якщо на одну подію підписано кілька обробників — їхній час підсумовується.
  3. Presentation Delay (затримка відображення) — час від завершення обробників до моменту, коли браузер малює наступний кадр. Сюди входять перерахунок стилів, layout, paint і композитинг.

Порогові значення

  • Добре: менше 200 мс — сайт відчувається миттєво чуйним
  • Потребує покращення: 200–500 мс — користувач вже помічає затримки
  • Погано: понад 500 мс — сайт відчувається як завислий, і це реально фруструє

Чим INP відрізняється від FID

FID вимірював лише Input Delay першої взаємодії — тобто тільки першу фазу і тільки для першого кліку. Що це означало на практиці? Сайт міг мати чудовий FID, але жахливу чуйність при подальших взаємодіях: відкриття фільтрів, переключення вкладок, надсилання форм — усе це FID просто ігнорував.

INP вимірює всі три фази для всіх взаємодій. Набагато чесніша картина.

Вплив на бізнес

Чуйність сайту безпосередньо впливає на поведінку користувачів і бізнес-метрики:

  • Сайти з хорошими Core Web Vitals мають на 24% нижчий bounce rate
  • E-commerce проєкти фіксують зростання конверсій на 15–30% після оптимізації INP
  • RedBus отримав 7% зростання продажів після покращення INP
  • Google використовує Core Web Vitals як сигнал ранжування — повільні сайти просто втрачають позиції у видачі

Як діагностувати проблеми з INP

Перш ніж щось оптимізувати, потрібно зрозуміти, де саме проблема. Добра новина — у 2026 році інструменти для діагностики INP стали досить зрілими.

Chrome DevTools — панель Performance

Найшвидший спосіб почати. Відкрийте DevTools (F12), перейдіть на вкладку Performance, увімкніть "Web Vitals" і запишіть трейс під час взаємодії зі сторінкою. DevTools покаже кожну взаємодію, позначить ті, що перевищують поріг, і розкладе на три фази: Input Delay, Processing Time та Presentation Delay. Ви одразу побачите, яка фаза є вузьким місцем.

PageSpeed Insights

PageSpeed Insights показує дані з Chrome User Experience Report (CrUX) — це реальні польові дані від користувачів Chrome. Тут видно значення INP на 75-му перцентилі для вашого сайту і розподіл за категоріями. Для SEO ці дані найрелевантніші, бо саме їх Google використовує для ранжування.

Google Search Console

У розділі "Core Web Vitals" Search Console дає агреговану картину по всьому сайту: скільки URL мають хороший, посередній і поганий INP. Найкращий інструмент для моніторингу прогресу на рівні цілого проєкту та виявлення проблемних груп сторінок.

Бібліотека web-vitals

Бібліотека web-vitals від Google — стандартний спосіб збирати дані про Core Web Vitals від реальних користувачів (Real User Monitoring). Версія з атрибуцією дає детальний розклад кожної взаємодії:

import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  const { value, attribution } = metric;
  const {
    interactionTarget,
    interactionType,
    inputDelay,
    processingDuration,
    presentationDelay,
    longAnimationFrameEntries
  } = attribution;

  console.group(`INP: ${value.toFixed(0)}ms (${metric.rating})`);
  console.log(`Елемент: ${interactionTarget}`);
  console.log(`Тип взаємодії: ${interactionType}`);
  console.log(`Input Delay: ${inputDelay.toFixed(0)}ms`);
  console.log(`Processing Duration: ${processingDuration.toFixed(0)}ms`);
  console.log(`Presentation Delay: ${presentationDelay.toFixed(0)}ms`);

  if (longAnimationFrameEntries.length > 0) {
    const loaf = longAnimationFrameEntries[0];
    console.log(`LoAF тривалість: ${loaf.duration}ms`);
    console.log(`Скрипти:`, loaf.scripts);
  }
  console.groupEnd();

  // Надсилання даних у систему аналітики
  navigator.sendBeacon('/api/vitals', JSON.stringify({
    metric: 'INP',
    value,
    rating: metric.rating,
    target: interactionTarget,
    inputDelay,
    processingDuration,
    presentationDelay
  }));
});

Long Animation Frames API (LoAF) — золотий стандарт 2026 року

Long Animation Frames API (LoAF, вимовляється "лоуф") — це еволюція Long Tasks API, і чесно кажучи, різниця між ними величезна. API з'явився в Chrome 123 і надає значно детальнішу інформацію.

Головна перевага: LoAF показує не просто "був довгий фрейм", а конкретно які скрипти його спричинили, скільки часу зайняла кожна частина рендерингу, і яка URL скрипту є винуватцем. Фрейми тривалістю понад 50 мс вважаються "довгими" і потенційно проблемними.

// Спостереження за довгими анімаційними фреймами
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // entry.duration — загальна тривалість фрейму
    // entry.blockingDuration — час, протягом якого фрейм блокував введення
    console.log(`Довгий фрейм: ${entry.duration}ms`);
    console.log(`Тривалість блокування: ${entry.blockingDuration}ms`);

    // Детальна інформація про кожен скрипт, що спричинив затримку
    for (const script of entry.scripts) {
      console.log(`  Скрипт: ${script.sourceURL}`);
      console.log(`  Функція: ${script.sourceFunctionName}`);
      console.log(`  Тривалість: ${script.duration}ms`);
      console.log(`  Тип виклику: ${script.invokerType}`);
      // invokerType може бути: "user-callback", "event-listener",
      // "resolve-promise", "classic-script", "module-script"
    }
  }
});

// Перевірка підтримки API перед використанням
if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  observer.observe({ type: 'long-animation-frame', buffered: true });
} else {
  console.warn('Long Animation Frames API не підтримується в цьому браузері');
}

LoAF особливо корисний для виявлення сторонніх скриптів, які непомітно вбивають ваш INP — чат-віджети, аналітика, рекламні мережі. Ви точно побачите URL скрипту і його внесок у загальну затримку. Мій досвід показує, що саме ці "невидимі" скрипти часто є головною причиною проблем.

Основні причини поганого INP

Розуміння типових причин — це вже половина рішення. Ось п'ять найпоширеніших "вбивць" INP у 2026 році.

1. Довгі задачі JavaScript, що блокують головний потік

Причина номер один, без перебільшення. Коли JavaScript виконує тривалу синхронну операцію (понад 50 мс), головний потік зайнятий і не може обробити взаємодію користувача. Уявіть: людина натискає кнопку, а браузер "зависає", бо зайнятий обробкою масиву з 10 000 елементів. Усе це збільшує Input Delay — першу фазу INP.

2. Надмірно великий DOM

Великий DOM бʼє одразу по двох фазах INP: Processing Time (обробники подій працюють повільніше з великим DOM) і Presentation Delay (браузеру потрібно більше часу на перерахунок стилів і layout). Рекомендовані обмеження:

  • Загальна кількість вузлів: менше 1 500
  • Максимальна глибина дерева: не більше 32 рівнів
  • Максимальна кількість дочірніх елементів у батьківського вузла: не більше 60

Сторінки з 3 000–5 000 DOM-вузлів (а це типово для сайтів на популярних конструкторах) мають у 2–3 рази гіршу чуйність порівняно з оптимізованими сторінками.

3. Сторонні скрипти

Чат-віджети, аналітика, менеджери тегів, рекламні скрипти — все це виконується в головному потоці і конкурує з вашими обробниками подій за процесорний час. Один-єдиний чат-віджет може додати 100–300 мс до Input Delay, перетворюючи "добрий" INP на "поганий". Особливо підступні менеджери тегів (привіт, Google Tag Manager), які динамічно завантажують десятки додаткових скриптів.

4. Layout thrashing (каскадне переобчислення layout)

Layout thrashing — це коли JavaScript багаторазово читає і записує геометричні властивості DOM у циклі. Кожне читання (наприклад, offsetHeight) після запису (скажімо, зміни style.width) змушує браузер синхронно перерахувати layout. Операція, яка повинна займати 5 мс, раптом займає 200 мс і більше. Processing Time злітає.

5. Важкі конструктори сторінок (Page Builders)

Конструктори на кшталт Divi, Elementor та подібні генерують надмірно глибокий DOM із вкладеними обгортками, інлайн-стилями і власними JavaScript-фреймворками. Результат — одночасне погіршення всіх трьох фаз INP: великий JavaScript збільшує Input Delay, складні обробники збільшують Processing Time, а масивний DOM збільшує Presentation Delay. Тріо, від якого важко втекти, якщо ви прив'язані до такого конструктора.

Практичні техніки оптимізації

Ну що ж, перейдемо до конкретики. Які техніки реально працюють для покращення INP? Почнемо з найсучасніших і найефективніших.

scheduler.yield() — рекомендований підхід у 2026 році

Метод scheduler.yield() — сучасний API для поступки головним потоком (yielding to the main thread). Його ключова перевага перед setTimeout: після поступки продовження вашої задачі потрапляє на початок черги, а не в кінець. Ваш код не буде "покараний" за ввічливість — він відновить виконання раніше за інші задачі, при цьому давши браузеру обробити взаємодію користувача.

API підтримується в Chrome (з 2024 року) та Firefox (з серпня 2025). Safari поки що ні, тому потрібен fallback:

// Універсальна функція поступки з fallback
async function yieldToMain() {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  // Fallback для браузерів без підтримки scheduler.yield()
  return new Promise((resolve) => setTimeout(resolve, 0));
}

// Приклад: обробка великого масиву даних порціями
async function processLargeDataset(items) {
  const CHUNK_SIZE = 50;
  const results = [];

  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);

    // Обробка порції даних
    for (const item of chunk) {
      results.push(transformItem(item));
    }

    // Поступка головним потоком після кожної порції
    // Це дозволяє браузеру обробити кліки, натискання клавіш тощо
    await yieldToMain();
  }

  return results;
}

// Приклад: обробник кліку з поступкою між етапами
async function handleFilterClick(event) {
  // Етап 1: оновити стан UI негайно (візуальний зворотний зв'язок)
  showLoadingSpinner();

  await yieldToMain();

  // Етап 2: виконати фільтрацію даних
  const filteredData = filterProducts(allProducts, selectedFilters);

  await yieldToMain();

  // Етап 3: оновити DOM з результатами
  renderProducts(filteredData);

  hideLoadingSpinner();
}

requestIdleCallback для некритичної роботи

requestIdleCallback дозволяє виконувати роботу, коли браузер "нудьгує" — немає активних задач і до наступного кадру ще є вільний час. Ідеальний варіант для аналітики, попереднього завантаження даних та іншої роботи, яка не впливає на поточний UI:

// Відкладена відправка даних аналітики
function sendAnalytics(eventData) {
  if ('requestIdleCallback' in window) {
    requestIdleCallback((deadline) => {
      // deadline.timeRemaining() показує, скільки мс залишилось
      // до наступного фрейму (зазвичай до 50 мс)
      if (deadline.timeRemaining() > 10) {
        navigator.sendBeacon('/api/analytics', JSON.stringify(eventData));
      } else {
        // Якщо часу замало, відкладаємо ще раз
        sendAnalytics(eventData);
      }
    }, { timeout: 5000 }); // Максимальна затримка — 5 секунд
  } else {
    // Fallback
    setTimeout(() => {
      navigator.sendBeacon('/api/analytics', JSON.stringify(eventData));
    }, 0);
  }
}

// Приклад використання: після кліку
document.querySelector('.buy-button').addEventListener('click', (e) => {
  // Критична робота — одразу
  addToCart(e.target.dataset.productId);
  updateCartUI();

  // Некритична робота — коли браузер вільний
  sendAnalytics({ event: 'add_to_cart', productId: e.target.dataset.productId });
});

requestAnimationFrame для візуальних оновлень

requestAnimationFrame (rAF) виконує код безпосередньо перед наступним кадром відображення. Це оптимальний спосіб оновлювати UI після взаємодії — браузер гарантує, що ваш код виконається в правильний момент циклу рендерингу, без зайвих перерахунків layout:

// Замість прямого оновлення DOM в обробнику
button.addEventListener('click', () => {
  // Поганий підхід: прямі маніпуляції з DOM в обробнику
  // element.style.transform = 'translateX(100px)';

  // Хороший підхід: делегування візуальних оновлень до rAF
  requestAnimationFrame(() => {
    element.style.transform = 'translateX(100px)';
  });
});

Розбиття довгих задач за допомогою async/await

Навіть без scheduler.yield() можна розбити довгі синхронні операції. Використовуємо паттерн із async/await і бюджетом часу:

// Було: одна довга задача, що блокує потік на 400 мс
function processAllItems(items) {
  items.forEach(item => {
    heavyComputation(item);  // 400 мс разом
  });
}

// Стало: розбиваємо на менші задачі з поступкою
async function processAllItems(items) {
  const deadline = performance.now() + 50; // 50 мс бюджет

  for (const item of items) {
    heavyComputation(item);

    // Якщо бюджет часу вичерпано — поступаємось
    if (performance.now() >= deadline) {
      await yieldToMain(); // Використовуємо нашу функцію з попереднього прикладу
    }
  }
}

Web Workers для важких обчислень

Якщо обчислення дійсно важкі — парсинг великих JSON, криптографія, обробка зображень — виносіть їх у Web Worker. Це окремий потік, який взагалі не блокує головний потік і ніяк не впливає на INP:

// main.js — головний потік
const worker = new Worker('/heavy-computation-worker.js');

document.querySelector('.process-btn').addEventListener('click', () => {
  // UI залишається чуйним, бо обчислення в іншому потоці
  showLoadingSpinner();
  worker.postMessage({ data: largeDataset });
});

worker.addEventListener('message', (event) => {
  const result = event.data;
  renderResults(result);
  hideLoadingSpinner();
});

// heavy-computation-worker.js — окремий потік
self.addEventListener('message', (event) => {
  const { data } = event.data;
  const result = performHeavyComputation(data);
  self.postMessage(result);
});

CSS contain для ізоляції секцій

Властивість CSS contain каже браузеру, що елемент ізольований від решти сторінки щодо layout, стилів або малювання. Браузер може оптимізувати перерахунки і зменшити Presentation Delay:

/* Ізоляція секцій: браузер не перераховує layout усієї сторінки
   при зміні вмісту цієї секції */
.product-card {
  contain: strict; /* layout + style + paint + size */
}

/* Для списків і каруселей */
.product-list {
  contain: content; /* layout + style + paint */
}

/* content-visibility: auto — браузер не рендерить елементи поза viewport */
.below-fold-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* Приблизна висота для запобігання layout shift */
}

Негайний візуальний зворотний зв'язок

Одна з найефективніших стратегій (і водночас найпростіших) — показати користувачу негайний результат його дії, навіть якщо повна обробка ще не завершена. Skeleton-loaders, спінери, зміна стану кнопки — все це сигналізує: "Ваша дія прийнята, результат на підході".

Технічно це не зменшує значення INP напряму, але суттєво покращує сприйняття чуйності. А ще — допомагає зменшити INP, якщо ви правильно структуруєте код:

// Негайний зворотний зв'язок + відкладена важка робота
document.querySelector('.search-btn').addEventListener('click', async () => {
  // 1. Негайний візуальний зворотний зв'язок (мінімальний Processing Time)
  const resultContainer = document.getElementById('results');
  resultContainer.innerHTML = `
    <div class="skeleton-loader">
      <div class="skeleton-line"></div>
      <div class="skeleton-line"></div>
      <div class="skeleton-line"></div>
    </div>
  `;

  // 2. Поступка потоком — дозволяє браузеру відобразити skeleton
  await yieldToMain();

  // 3. Виконання пошуку (Processing Time не впливає на INP,
  //    бо кадр уже було відображено)
  const results = await fetchSearchResults(query);

  // 4. Оновлення DOM реальними результатами
  renderSearchResults(results, resultContainer);
});

Оптимізація INP у популярних фреймворках

Кожен сучасний фреймворк має вбудовані інструменти для покращення чуйності. Немає сенсу винаходити велосипед — краще використати те, що вже є.

React: useDeferredValue, useTransition, React.memo

React 18+ дає два ключові хуки для управління пріоритетами оновлень:

  • useTransition — позначає оновлення стану як "низькопріоритетне". React продовжить показувати попередній UI, поки нове оновлення готується у фоні. Чуйність при дорогих ре-рендерах зберігається.
  • useDeferredValue — відкладає оновлення конкретного значення, даючи React змогу спочатку оновити критичні частини UI.
  • React.memo — запобігає зайвим ре-рендерам компонентів, коли пропси не змінилися. Класика, але працює.
import { useState, useTransition, useDeferredValue, memo } from 'react';

// Мемоїзований компонент — не ре-рендериться, якщо props не змінились
const ProductCard = memo(function ProductCard({ product }) {
  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{product.price} грн</p>
    </div>
  );
});

function ProductFilter({ products }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [filteredProducts, setFilteredProducts] = useState(products);

  function handleSearch(e) {
    const value = e.target.value;
    // Негайне оновлення — поле введення реагує миттєво
    setQuery(value);

    // Відкладене оновлення — фільтрація не блокує введення
    startTransition(() => {
      const filtered = products.filter(p =>
        p.name.toLowerCase().includes(value.toLowerCase())
      );
      setFilteredProducts(filtered);
    });
  }

  return (
    <div>
      <input value={query} onChange={handleSearch} placeholder="Пошук..." />
      {isPending && <div className="spinner" />}
      <div className="product-list">
        {filteredProducts.map(p => (
          <ProductCard key={p.id} product={p} />
        ))}
      </div>
    </div>
  );
}

Vue: v-memo, асинхронні компоненти

Vue 3 має свій набір інструментів:

  • v-memo — кешує рендеринг частини шаблону, пропускаючи оновлення, якщо вказані залежності не змінились. Для великих списків — просто знахідка.
  • defineAsyncComponent — завантажує компоненти лише коли вони потрібні, зменшуючи обсяг JavaScript при завантаженні сторінки.
  • Reactive computed з debounce — для фільтрації та пошуку без блокування введення.

Angular: OnPush, trackBy, @defer

Angular теж має потужні вбудовані механізми:

  • OnPush Change Detection — компонент оновлюється тільки при зміні inputs або при події всередині нього. Кількість перевірок при кожному циклі change detection зменшується кардинально.
  • trackBy — у циклах *ngFor (або @for у нових шаблонах) дозволяє Angular перевикористовувати DOM-елементи замість їх повного перестворення.
  • @defer — вбудований механізм ленівого завантаження частин шаблону. Контент всередині @defer завантажується та рендериться лише за певних умов (потрапляє у viewport, спрацьовує таймер тощо).

Порівняння стратегій поступки головним потоком

Різні API для поступки головним потоком поводяться принципово по-різному. Ось порівняння, яке варто зберегти в закладки:

Стратегія Пріоритет продовження Мінімальна затримка Підтримка браузерів Коли використовувати
scheduler.yield() Високий (початок черги) ~0 мс Chrome, Firefox Розбиття довгих задач без втрати пріоритету
setTimeout(fn, 0) Низький (кінець черги) 4–10 мс (до 1000 мс у фоновій вкладці) Усі браузери Fallback, коли scheduler.yield() недоступний
requestIdleCallback Найнижчий (лише під час простою) Невизначена (може бути дуже великою) Chrome, Firefox, Safari 16.4+ Некритична робота: аналітика, prefetch, логування
requestAnimationFrame Синхронізований з рендерингом ~16 мс (залежить від частоти оновлення екрану) Усі браузери Візуальні оновлення, анімації, DOM-операції

Ключова різниця між scheduler.yield() і setTimeout: при використанні setTimeout продовження вашої задачі потрапляє в кінець черги. Інші скрипти — реклама, аналітика, інші обробники — можуть виконатись раніше, і ваша задача просто чекатиме. З scheduler.yield() продовження потрапляє на початок черги, тому код відновиться першим після обробки взаємодії. Для складних додатків ця різниця критична.

Чекліст оптимізації INP

Систематичний підхід завжди дає найкращі результати. Ось покроковий чекліст, упорядкований за пріоритетом — від найбільшого впливу до найменшого:

  1. Визначте найповільнішу взаємодію — використайте web-vitals з атрибуцією або LoAF API, щоб знайти конкретний елемент і тип взаємодії з найгіршим INP.
  2. Визначте проблемну фазу — розкладіть INP на три фази (Input Delay, Processing Time, Presentation Delay) і сфокусуйтесь на найдовшій.
  3. Розбийте довгі задачі JavaScript — використайте scheduler.yield() (з fallback на setTimeout) для задач, що виконуються понад 50 мс.
  4. Аудит сторонніх скриптів — через LoAF API визначте, які скрипти блокують головний потік, і видаліть або відкладіть непотрібні. Завантажуйте чат-віджети та аналітику після взаємодії або через requestIdleCallback.
  5. Зменшіть розмір DOM — тримайте кількість вузлів нижче 1 500. Використовуйте content-visibility: auto для секцій нижче згину. Для довгих списків розгляньте віртуалізацію.
  6. Винесіть важкі обчислення у Web Workers — парсинг JSON, обробка даних, складні розрахунки не повинні жити в головному потоці.
  7. Забезпечте негайний візуальний зворотний зв'язок — показуйте skeleton-loaders або спінери одразу після взаємодії, перш ніж запускати важку роботу.
  8. Використовуйте фреймворк-специфічні інструменти — React: useTransition, useDeferredValue; Vue: v-memo, async components; Angular: OnPush, @defer.
  9. Усуніть layout thrashing — групуйте операції читання і запису DOM. Використовуйте requestAnimationFrame для візуальних оновлень. Додайте CSS contain для ізоляції секцій.
  10. Налаштуйте моніторинг — впровадьте збір INP від реальних користувачів (RUM) із розкладом по фазах і регулярно аналізуйте тренди. Лабораторні тести — це добре, але тільки польові дані показують реальну картину.

Часті запитання (FAQ)

Яка різниця між INP та FID?

FID (First Input Delay) вимірював затримку введення лише для першої взаємодії і враховував лише одну фазу — Input Delay. INP (Interaction to Next Paint) вимірює повний час відгуку (усі три фази) для всіх взаємодій протягом сеансу і звітує найгірший результат на 75-му перцентилі. Тому сайт міг мати відмінний FID, але провалювати INP: перша взаємодія швидка (головний потік ще не перевантажений), а подальші — повільні через накопичені скрипти і складну логіку.

Який хороший показник INP?

Хороший показник — менше 200 мс на 75-му перцентилі. Це означає, що 75% взаємодій ваших користувачів отримують візуальний відгук швидше ніж за 200 мілісекунд. Від 200 до 500 мс — потребує покращення, понад 500 мс — погано. Важливий момент: для ранжування Google використовує саме 75-й перцентиль польових даних із CrUX, не середнє значення і не лабораторні тести.

Чому мій сайт провалює INP, хоча FID був у нормі?

Це, мабуть, найпоширеніша ситуація. Причин кілька. По-перше, FID вимірював лише першу взаємодію — зазвичай клік по навігації під час завантаження, коли JavaScript ще не встиг повністю виконатись. INP вимірює всі взаємодії, включно з тими, що відбуваються після повного завантаження: фільтрація товарів, відкриття випадаючих списків, заповнення форм.

По-друге, FID ігнорував Processing Time і Presentation Delay. Навіть якщо ваш обробник кліку працював 400 мс — FID це не помічав. INP враховує всі три фази, і будь-яка повільність стає видимою.

Як scheduler.yield() допомагає покращити INP?

scheduler.yield() дозволяє розбити довгу задачу на менші частини, повертаючи контроль головному потоку між ними. Коли ваш код виконує await scheduler.yield(), браузер отримує можливість обробити очікувані взаємодії, перш ніж код продовжить виконання. Головна перевага перед setTimeout(fn, 0): продовження задачі потрапляє на початок черги, а не в кінець. Код відновиться одразу після обробки взаємодій — без зайвого очікування на аналітику, рекламу та інші скрипти. Результат: менша Input Delay і швидше завершення вашої задачі.

Як Long Animation Frames API допомагає діагностувати проблеми з INP?

Long Animation Frames API (LoAF) — найдетальніший інструмент для діагностики проблем із чуйністю, доступний у Chrome з версії 123. На відміну від Long Tasks API, який лише казав "була довга задача тривалістю N мс", LoAF дає повну картину: тривалість фрейму, тривалість блокування, і (найважливіше) масив scripts з інформацією про кожен скрипт-винуватець.

Для кожного скрипту ви отримуєте URL файлу, ім'я функції, тривалість виконання і тип виклику. Це дозволяє сказати конкретно: "Чат-віджет від провайдера X виконує обробник resize протягом 180 мс і блокує кліки по кнопці купівлі". Бібліотека web-vitals автоматично включає LoAF-записи в атрибуцію INP, що робить весь процес діагностики ще зручнішим.

Про Автора Editorial Team

Our team of expert writers and editors.