W marcu 2024 roku Google oficjalnie zastąpił First Input Delay (FID) nową metryką Interaction to Next Paint (INP) jako część Core Web Vitals. INP lepiej odzwierciedla rzeczywistą responsywność strony i jest trudniejsza do optymalizacji. Ten przewodnik wyjaśnia różnice i pokazuje jak poprawić wyniki.
Czym było FID?
First Input Delay mierzył czas od pierwszej interakcji użytkownika (kliknięcie, tap) do momentu, gdy przeglądarka mogła rozpocząć obsługę tego zdarzenia. Mierzył tylko pierwszą interakcję.
Ograniczenia FID
- Tylko pierwsza interakcja - ignorował wszystkie kolejne
- Tylko opóźnienie - nie mierzył czasu przetwarzania
- Łatwy do optymalizacji - wystarczyło szybkie pierwsze ładowanie
- Nie odzwierciedlał rzeczywistości - strona mogła być wolna po pierwszej interakcji
Czym jest INP?
Interaction to Next Paint mierzy responsywność strony przez cały czas jej użytkowania. Bierze pod uwagę wszystkie interakcje (kliknięcia, tappy, naciśnięcia klawiszy) i raportuje najgorszą z nich (technicznie: 98. percentyl).
Co mierzy INP?
INP mierzy czas od interakcji do momentu wyrenderowania następnej klatki:
INP = Input Delay + Processing Time + Presentation Delay
- Input Delay - czas oczekiwania na rozpoczęcie obsługi (jak FID)
- Processing Time - czas wykonania event handlerów
- Presentation Delay - czas renderowania zmian w DOM
Progi INP
| Wynik | Ocena |
|---|---|
| ≤ 200ms | Dobry (zielony) |
| 200ms - 500ms | Wymaga poprawy (pomarańczowy) |
| > 500ms | Słaby (czerwony) |
Porównanie FID vs INP
| Aspekt | FID | INP |
|---|---|---|
| Co mierzy | Tylko opóźnienie | Opóźnienie + przetwarzanie + renderowanie |
| Które interakcje | Tylko pierwszą | Wszystkie (raportuje najgorszą) |
| Progi | ≤100ms dobry | ≤200ms dobry |
| Trudność optymalizacji | Niska | Wysoka |
| Status | Wycofany (marzec 2024) | Aktualny Core Web Vital |
Jak mierzyć INP?
Dane laboratoryjne
Lighthouse (Chrome DevTools):
- INP nie jest bezpośrednio mierzony w Lighthouse
- Użyj Total Blocking Time (TBT) jako proxy
Chrome DevTools - Performance:
- Otwórz Performance panel
- Nagrywaj podczas interakcji z stroną
- Szukaj długich tasków w Main thread
Dane rzeczywiste (Field data)
web-vitals library:
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value);
console.log('Rating:', metric.rating); // good, needs-improvement, poor
console.log('Entries:', metric.entries); // Szczegóły interakcji
});
Google Search Console:
- Core Web Vitals report pokazuje INP z danych CrUX
PageSpeed Insights:
- Sekcja “Diagnoza problemów z wydajnością” pokazuje INP
Chrome DevTools - Web Vitals overlay
- Otwórz DevTools
- Naciśnij Ctrl+Shift+P
- Wpisz “Show Web Vitals”
- Interakcja ze stroną pokaże INP w czasie rzeczywistym
Przyczyny słabego INP
1. Długie taski JavaScript
JavaScript blokuje main thread. Każdy task >50ms to “long task” wpływający na INP.
// Źle - długi synchroniczny task
function processData(items) {
items.forEach(item => {
// Ciężkie obliczenia...
heavyComputation(item);
});
}
// Dobrze - podziel na mniejsze taski
async function processDataAsync(items) {
for (const item of items) {
heavyComputation(item);
// Oddaj kontrolę przeglądarce
await scheduler.yield();
}
}
// Alternatywnie z setTimeout
function processDataChunked(items, chunkSize = 10) {
let index = 0;
function processChunk() {
const chunk = items.slice(index, index + chunkSize);
chunk.forEach(heavyComputation);
index += chunkSize;
if (index < items.length) {
setTimeout(processChunk, 0);
}
}
processChunk();
}
2. Ciężkie event handlery
// Źle - ciężki handler blokuje rendering
button.addEventListener('click', () => {
// 500ms obliczeń...
processLargeDataset();
updateUI();
});
// Dobrze - oddziel obliczenia od UI
button.addEventListener('click', async () => {
// Natychmiast pokaż feedback
button.disabled = true;
showSpinner();
// Obliczenia w następnym tasku
await scheduler.yield();
const result = processLargeDataset();
// Update UI
await scheduler.yield();
updateUI(result);
hideSpinner();
button.disabled = false;
});
3. Zbyt wiele re-renderów
// Źle - każda zmiana triggeruje reflow
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
container.appendChild(div); // Reflow!
});
// Dobrze - batch DOM operations
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
fragment.appendChild(div);
});
container.appendChild(fragment); // Jeden reflow
4. Layout thrashing
// Źle - wielokrotne odczyty i zapisy
elements.forEach(el => {
const height = el.offsetHeight; // Odczyt (force layout)
el.style.height = height + 10 + 'px'; // Zapis (invalidate layout)
});
// Dobrze - najpierw odczytaj, potem zapisz
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px';
});
5. Third-party scripts
Skrypty firm trzecich (analytics, ads, chat widgets) często mają ciężkie handlery.
Techniki optymalizacji INP
1. Scheduler API (scheduler.yield)
Nowe API pozwalające oddać kontrolę przeglądarce:
async function handleClick() {
// Krok 1
await scheduler.yield();
// Krok 2
await scheduler.yield();
// Krok 3
}
// Polyfill dla starszych przeglądarek
if (!('scheduler' in window)) {
window.scheduler = {
yield: () => new Promise(resolve => setTimeout(resolve, 0))
};
}
2. requestIdleCallback
Wykonuj mniej pilne taski gdy przeglądarka jest idle:
// Przetwarzanie w tle
function processInBackground(items) {
let index = 0;
function processNext(deadline) {
while (index < items.length && deadline.timeRemaining() > 0) {
processItem(items[index]);
index++;
}
if (index < items.length) {
requestIdleCallback(processNext);
}
}
requestIdleCallback(processNext);
}
3. requestAnimationFrame
Synchronizuj zmiany wizualne z frame rate:
// Źle - może trafić w środek frame
button.addEventListener('click', () => {
element.style.transform = 'translateX(100px)';
});
// Dobrze - synchronizacja z frame
button.addEventListener('click', () => {
requestAnimationFrame(() => {
element.style.transform = 'translateX(100px)';
});
});
4. Web Workers
Przenieś ciężkie obliczenia poza main thread:
// main.js
const worker = new Worker('worker.js');
button.addEventListener('click', () => {
showSpinner();
worker.postMessage({ data: largeDataset });
});
worker.onmessage = (e) => {
hideSpinner();
displayResults(e.data);
};
// worker.js
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};
5. Debouncing i throttling
// Debounce - wykonaj po zakończeniu sekwencji
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
// Throttle - wykonuj maksymalnie raz na X ms
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Użycie
input.addEventListener('input', debounce(handleSearch, 300));
window.addEventListener('scroll', throttle(handleScroll, 100));
6. Passive event listeners
// Źle - może blokować scrollowanie
element.addEventListener('touchstart', handler);
// Dobrze - deklaracja że nie będzie preventDefault
element.addEventListener('touchstart', handler, { passive: true });
7. CSS contain
Ogranicz obszar, który musi być przeliczony:
.widget {
contain: content; /* Izoluje layout i paint */
}
.modal {
contain: strict; /* Pełna izolacja */
}
8. content-visibility
Opóźnij renderowanie elementów poza viewport:
.card {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Szacowana wysokość */
}
Optymalizacja frameworków
React
// Użyj startTransition dla mniej pilnych aktualizacji
import { startTransition } from 'react';
function handleClick() {
// Pilna aktualizacja - natychmiast
setIsLoading(true);
// Mniej pilna - może być odroczona
startTransition(() => {
setResults(computeExpensiveResults());
});
}
// useDeferredValue dla drogich renderów
import { useDeferredValue } from 'react';
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
// Renderuj z opóźnionym query
return <Results query={deferredQuery} />;
}
// memo dla unikania zbędnych re-renderów
const ExpensiveComponent = React.memo(({ data }) => {
// ...
});
Vue
<script setup>
import { computed, shallowRef } from 'vue';
// shallowRef dla dużych obiektów
const largeData = shallowRef(null);
// Lazy computed
const expensiveComputed = computed(() => {
// Obliczenia tylko gdy potrzebne
});
</script>
<template>
<!-- v-once dla statycznej treści -->
<div v-once>{{ staticContent }}</div>
<!-- v-memo dla cachowania -->
<div v-for="item in list" :key="item.id" v-memo="[item.id]">
{{ item.name }}
</div>
</template>
Checklist optymalizacji INP
JavaScript
- Podziel długie taski na mniejsze (poniżej 50ms)
- Użyj scheduler.yield() lub setTimeout(0)
- Przenieś ciężkie obliczenia do Web Workers
- Debounce/throttle event handlerów
DOM
- Batch DOM operations (DocumentFragment)
- Unikaj layout thrashing
- Użyj requestAnimationFrame dla animacji
- Zastosuj CSS contain dla izolacji
Third-party
- Defer nieistotne skrypty
- Lazy-load widgety (chat, social)
- Rozważ server-side GTM
Framework
- React: startTransition, useDeferredValue
- Vue: v-once, v-memo, shallowRef
- Virtualizacja długich list
Monitorowanie INP
Real User Monitoring (RUM)
import { onINP } from 'web-vitals';
onINP((metric) => {
// Wyślij do analytics
gtag('event', 'web_vitals', {
metric_name: metric.name,
metric_value: metric.value,
metric_rating: metric.rating,
metric_delta: metric.delta
});
// Lub do własnego endpointa
navigator.sendBeacon('/analytics', JSON.stringify({
name: 'INP',
value: metric.value,
page: location.pathname
}));
});
Alerty na słabe INP
Ustaw alerty w Google Search Console lub własnym systemie monitoringu gdy INP przekroczy próg.
Podsumowanie
INP jest trudniejszą metryką niż FID, ale lepiej odzwierciedla rzeczywiste doświadczenie użytkownika. Kluczowe punkty:
- INP mierzy całą interakcję - nie tylko opóźnienie
- Raportuje najgorszą interakcję - musisz optymalizować wszystko
- Podziel długie taski - żaden task nie powinien trwać >50ms
- Oddawaj kontrolę przeglądarce - scheduler.yield(), requestIdleCallback
- Web Workers - dla ciężkich obliczeń
- Monitoruj w RUM - dane laboratoryjne nie wystarczą
Cel to INP poniżej 200ms dla 75% użytkowników.
Więcej o tym, jak INP i pozostałe metryki wydajności wpływają na ranking w Google, znajdziesz w przewodniku po technologiach web i SEO.
Źródła
-
web.dev - Interaction to Next Paint (INP) https://web.dev/articles/inp
-
web.dev - Optimize INP https://web.dev/articles/optimize-inp
-
Chrome Developers - INP announcement https://developer.chrome.com/blog/inp-cwv
-
web.dev - Long tasks https://web.dev/articles/optimize-long-tasks
-
MDN - Scheduler API https://developer.mozilla.org/en-US/docs/Web/API/Scheduler
-
web.dev - First Input Delay (FID) - archived https://web.dev/articles/fid



