INP вместо FID: что готовить
В марте 2024 года Google заменил First Input Delay (FID) на Interaction to Next Paint (INP) в Core Web Vitals. К 2026 году все продукты, на которые я смотрю в работе, уже учитывают INP в SEO-стратегии. Расскажу, чем INP отличается от FID, как его измерять и какие техники реально помогают.
FID vs INP: ключевая разница
FID мерил задержку перед обработчиком первого взаимодействия. То есть «сколько прошло между кликом и началом выполнения JS». Если у тебя обработчик не блокировал главный поток — FID был отличный, даже если страница потом полминуты «думала» над результатом.
INP мерит общую задержку до следующего отрисованного кадра. То есть «сколько прошло между взаимодействием и тем, как пользователь увидел результат». Это включает и обработку, и обновление DOM, и рендер кадра.
Цели INP: ≤200 мс — хорошо, 200-500 мс — нужно улучшать, >500 мс — плохо.
В отличие от FID, INP смотрит на худший процент взаимодействий за всю сессию пользователя. То есть если 99% кликов отрабатывают за 50 мс, а один за 800 мс — твой INP будет ближе к 800 мс.
Как измерить
В реальной аудитории
Search Console → Core Web Vitals report показывает разбивку по INP. Это самый точный источник.
Через JavaScript на самом сайте:
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value);
// отправить в аналитику
navigator.sendBeacon('/analytics', JSON.stringify(metric));
});Метрика отправляется один раз на загрузку страницы. Если пользователь долго взаимодействует, обновление приходит позже.
В лаборатории
Lighthouse в DevTools показывает INP как часть Performance Insights. Но цифра там приблизительная — нужен реальный пользовательский ввод.
В Chrome DevTools на вкладке Performance можно записать сценарий взаимодействия и посмотреть детально, что блокирует кадр.
Что блокирует INP
Источники задержки:
- Обработчик события — если он делает много синхронной работы.
- Long tasks от других скриптов, которые блокируют главный поток в момент взаимодействия.
- Рендер кадра — если DOM-обновление вызывает дорогой layout/paint.
На моих проектах самые частые причины — третьи стороны (аналитика, баннеры) и тяжёлые React-компоненты, которые ререндерятся целиком.
Как уменьшить INP: тактики
1. Дробить тяжёлые задачи
Если обработчик клика делает 200 мс работы — это плохой INP. Разбей на части через scheduler.yield или requestIdleCallback:
async function applyFilter() {
setLoading(true);
await scheduler.yield(); // отдать управление браузеру
const filtered = items.filter(predicate);
await scheduler.yield();
setItems(filtered);
setLoading(false);
}scheduler.yield() — нативный API в Chrome 129+, в других — через polyfill scheduler-polyfill. Помогает раздробить длинные задачи на короткие, не теряя последовательности.
2. Откладывать некритичную работу
Если после клика нужно отправить аналитику и обновить UI — UI важнее. Сначала покажи результат, потом отправляй:
function onClick() {
setExpanded(true); // моментально
requestIdleCallback(() => {
track('expand', { id });
});
}requestIdleCallback запускает функцию когда браузер не занят. Аналитика прилетает позже, но пользователь уже видит результат.
3. Виртуализация списков
Если в списке 1000 элементов и клик на одном вызывает ререндер всего — это long task. Используй виртуализацию: TanStack Virtual или react-virtual. Рендерится только видимое окно, обновление быстрое.
4. Точечные ререндеры
В React: memo, useMemo, useCallback. В Zustand или Jotai — селекторы, чтобы компонент подписывался только на нужный кусок стейта.
На прошлом проекте у меня был INP 380 мс. Оказалось, при клике на один чекбокс ререндерился весь список из 800 элементов. После добавления memo и точечных селекторов — 90 мс.
5. Облегчить обработчики
Если в обработчике onChange input'а ты вызываешь синхронную валидацию через 500-строчную функцию — INP будет плохим. Замените на debounce + асинхронную валидацию через worker:
import { debounce } from 'lodash-es';
const validate = debounce(async (value: string) => {
const result = await runWorkerValidation(value);
setError(result);
}, 200);6. Ленивая гидрация
На Astro — это client:idle или client:visible вместо client:load. На Next с RSC — серверные компоненты вместо клиентских везде, где можно.
Цель: меньше JS гидрируется при первом взаимодействии, меньше задержки.
Третьи стороны
Аналитика, чаты, A/B-инструменты — типичные виновники плохого INP. Они часто перехватывают события или делают синхронную работу.
Стратегия:
- Загружать через
partytownв отдельный worker. - Откладывать загрузку до первого взаимодействия (
onClickна body). - Удалять то, чем команда реально не пользуется.
На одном проекте мы убрали Hotjar — INP упал на 60 мс. Команда полгода им не пользовалась.
React 19 и INP
React 19 принёс несколько улучшений, которые помогают:
useTransition— обновления, помеченные через transition, не блокируют срочные взаимодействия.- Улучшенный
concurrent rendering— React сам разбивает рендер на части. useDeferredValue— для значений, которые можно обновлять с задержкой.
Пример с useTransition:
const [isPending, startTransition] = useTransition();
function onSearch(query: string) {
setQuery(query); // срочно — для input'а
startTransition(() => {
setResults(filterItems(query)); // не срочно — фон
});
}Браузер успевает отрисовать обновлённый input до того, как React закончит фильтрацию. INP остаётся хорошим даже на больших списках.
Что я бы делал перед релизом
- Прогнать сценарии типичных взаимодействий: клик меню, открытие модалки, скролл с фильтрами.
- Записать Performance в DevTools, посмотреть длинные задачи.
- Применить тактики выше.
- Проверить в реальной аудитории через 2-3 дня после релиза.
Грабли
Лабораторные замеры обманывают. На моей машине с 32 ГБ RAM и i9 INP всегда «отлично», даже на плохо оптимизированных страницах. Throttle в DevTools (Slow 4G + 4× CPU) приближает к реальности, но всё равно не точно.
Реальные данные приходят с задержкой. Search Console обновляет CrUX данные раз в день, и для исправления нужно подождать 28 дней (окно метрики). Не паникуй после релиза — измерения окрепнут к концу месяца.
Что копать дальше
INP — это про общее ощущение «снаппиности» приложения. Если у тебя в реальной аудитории INP 300+ мс, это не «надо ускорить кнопку». Это сигнал, что где-то в продукте лежит большая задача, которая блокирует UI на долгие сотни миллисекунд. Найти её — самое ценное, что ты сделаешь для пользователей в этом квартале.