Core Web Vitals 2026: INP zastąpiło FID — co się zmieniło
INP (Interaction to Next Paint) zastąpiło FID jako Core Web Vital w marcu 2024. Różnice, pomiary, optymalizacja. Thresholds, real-field (CrUX), narzędzia.
Core Web Vitals 2026: INP zastąpiło FID — co się zmieniło
INP (Interaction to Next Paint) zastąpiło FID (First Input Delay) jako Core Web Vital 12 marca 2024. INP mierzy czas całego lifecycle interakcji (kliknięcie, tap, klawiatura): input delay + processing + presentation delay, w przeciwieństwie do FID, który mierzył tylko input delay pierwszej interakcji. Thresholds: < 200ms dobry, 200–500ms potrzebuje poprawy, > 500ms słaby. Dane mierzone real-field przez CrUX (Chrome User Experience Report), aproksymowane lab przez Lighthouse. INP mierzony jest na worst interaction w sesji (nie average). Optymalizacja: długie task breakdown, useDeferredValue, requestIdleCallback, scheduler.yield(), debouncing, async operations. Lepsze wyniki INP = lepszy ranking w Google (potwierdzone algorithm update marzec 2024). Dla engineerów tworzących content-heavy sites lub aplikacje SPA — INP to dziś twardy constraint, nie nice-to-have.
Jeśli pojęcia LCP, INP, CLS, TBT, TTFB są dla Ciebie nowe — zajrzyj do naszego Słownika SEO 2026, gdzie znajdziesz definicje 50 terminów (Core Web Vitals, E-E-A-T, AEO, CRO) z konkretnymi przykładami.
Dlaczego FID nie wystarczał
FID (First Input Delay) zadebiutował w 2020 roku jako pierwszy Core Web Vital mierzący interaktywność. Mierzył tylko:
- Delay między event (kliknięcie, tap) a browserem zaczynającym handle event
- Wyłącznie pierwszą interakcję w sesji
- Nie mierzył przetwarzania ani renderingu
Problemy FID były strukturalne i dobrze udokumentowane:
-
Selection bias. Wiele stron miało dobry FID tylko dlatego, że główny thread był mniej obciążony w momencie pierwszego kliknięcia (które zwykle następowało 2–3s po page load, gdy hydration już się zakończył). Późniejsze interakcje mogły być dramatycznie wolniejsze, ale FID już “passed”.
-
First interaction cherry-pick. Typowy scenariusz: FID = 50ms dla pojedynczego tapu “Akceptuję cookies”, ale jeśli kolejne taps w formularzu checkout były 300–500ms, FID tego nie pokazywał. Dla rzeczywistego UX to była katastrofa — dla Google Search Console wyglądało OK.
-
Brak pomiaru rendering. Event handler mógł być szybki (5ms), ale
setStatewywołujący re-render 5 komponentów React potrafił zablokować main thread na 200–400ms. FID kończył pomiar w momencie rozpoczęcia handlera — reszta lifecycle była niewidoczna. -
Brak związku z subiektywną responsywnością. User który klika i widzi feedback po 600ms ma zły UX — niezależnie od tego, w której fazie cyklu to opóźnienie nastąpiło. FID mierzył tylko jedną fazę i mylił programistów co do priorytetów optymalizacji.
Google ogłosił w maju 2023: INP od marca 2024 zastępuje FID. To był shift z first-interaction quick snapshot do session-level worst-case UX. Z perspektywy inżynierskiej — wreszcie metryka, która mierzy to, co użytkownik naprawdę czuje.
Jak działa INP
INP mierzy trzy fazy każdej interakcji:
-
Input delay — czas od user action (click/tap/key) do momentu, w którym przeglądarka zaczyna wykonywać event handler. Główny winowajca: długi main thread task blokujący event dispatch. Jeśli w momencie kliknięcia trwa task 300ms, input delay = pozostały czas tego taska.
-
Processing time — wykonanie event handlera JavaScript (twój kod React/Vue, analytics listener, third-party script listener). Tu jest najwięcej pola do optymalizacji i tu typowo kryją się problemy React appów.
-
Presentation delay — czas między zakończeniem handlera a kolejnym frame paint. Głównie: layout shifts, forced reflow, long-running
requestAnimationFramecallbacks, heavy CSS recalculation.
Suma tych trzech = single interaction latency.
INP session value
- Wszystkie interakcje w sesji są rejestrowane przez PerformanceObserver.
- Wybierana jest worst interaction (nie mean, nie median) — to kluczowa różnica względem FID.
- Dla sesji < 50 interakcji — brany jest maximum.
- Dla 50–200 interakcji — 98. percentyl.
- Dla sesji > 200 interakcji — 98. percentyl z wykluczeniem outlierów.
Porównanie threshold
| Metryka | Dobry | Wymaga poprawy | Słaby |
|---|---|---|---|
| FID (deprecated) | < 100ms | 100–300ms | > 300ms |
| INP | < 200ms | 200–500ms | > 500ms |
| LCP | < 2.5s | 2.5–4s | > 4s |
| CLS | < 0.1 | 0.1–0.25 | > 0.25 |
INP threshold jest bardziej permissive niż FID, ponieważ mierzy cały cykl — input delay + processing + presentation — a nie tylko pierwszą fazę. Google skalibrował progi tak, aby realnie przeszło ~75% dobrze zoptymalizowanych stron.
Pomiar INP — narzędzia
Real-field (CrUX — Chrome User Experience Report)
- 28-day rolling window anonimowych pomiarów od realnych użytkowników Chrome
- Dostępne przez: PageSpeed Insights, Search Console (Core Web Vitals report), Lighthouse CI, CrUX API (BigQuery dataset), CrUX Dashboard
- To jest oficjalny ranking signal — lab data nie wpływa na pozycje w Google
CrUX agreguje dane tylko dla stron z wystarczającym ruchem (progowo ~kilkadziesiąt sesji dziennie). Dla małych stron często brakuje CrUX data — wtedy Google fallbackuje do origin-level aggregation.
Lab (Lighthouse)
- Synthetic test z simulowanym CPU slowdown (4x) + emulacją Moto G4
- Dostępne: Chrome DevTools, Lighthouse CLI, web.dev/measure, PageSpeed Insights (sekcja “Diagnose performance issues”)
- NIE mierzy real INP — aproksymuje przez Total Blocking Time (TBT), który koreluje, ale nie jest 1:1
- Dobry dla dev loop (deterministyczny), ale nie oficjalny ranking signal
RUM (Real User Monitoring)
- Własne pomiary przez
PerformanceObserver('event')w przeglądarce - Popularne biblioteki:
web-vitals(Google), Chrome User Experience Report API - Pozwala trackować INP per page, per user segment, per device class
- Integracja z: Sentry, Cloudflare Web Analytics, Datadog RUM, New Relic Browser, Vercel Analytics
Code example — pomiar INP w kodzie
import { onINP } from 'web-vitals';
onINP((metric) => { console.log('INP:', metric.value, 'ms', metric.rating); // Send to analytics endpoint navigator.sendBeacon('/api/analytics', JSON.stringify({ metric: 'INP', value: metric.value, rating: metric.rating, // 'good' | 'needs-improvement' | 'poor' url: window.location.pathname, // Attribution data — który element i entry type interactionTarget: metric.attribution?.interactionTarget, eventType: metric.attribution?.eventType, }));});Ważne: web-vitals od v3 zwraca też obiekt attribution — pozwala zidentyfikować konkretny element DOM i event type który spowodował najgorszy INP. To gamechanger przy debugowaniu — zamiast “INP = 450ms gdzieś na stronie” widzisz “button#checkout, click, 450ms”.
Najczęstsze przyczyny słabego INP
1. Long tasks na main thread
Task > 50ms blokuje event dispatch → input delay rośnie liniowo. W DevTools Performance panel włącz “Long Tasks” overlay — zobaczysz czerwone trójkąty nad każdym taskiem > 50ms. Najczęstsze źródła: hydration React, parsowanie dużego JSON, synchroniczne fetch() w service worker.
2. Heavy event handler
Klasyczne anty-patterny:
- Synchroniczny
setStatew React wywołujący re-render drzewa > 100 komponentów JSON.parsedużego obiektu w response klik-handlera- Complex sorting/filtering w
onChange(np. search-as-you-type po 10 000 rekordach bez virtualizacji) - Synchroniczne zapisywanie do
localStoragedużych obiektów (serializacja blokuje main thread)
3. Layout thrashing
Forced synchronous layout — czytanie z DOM właściwości typu offsetTop, offsetHeight, getBoundingClientRect() w pętli przeplatanej z zapisami do DOM. Przeglądarka musi reflow po każdym odczycie. W DevTools Performance widać to jako fioletowe “Layout” bloki na timeline.
4. Third-party scripts
Analytics (GA4, FB Pixel, Hotjar), chat widgets (Intercom, Drift), payment iframes (Stripe.js), consent managers (Cookiebot, OneTrust). Każdy z nich może dodać 100–300ms do głównego wątku przy load. Największy offender: legacy GA + Tag Manager bez async, blokujące 500ms+.
5. Large React/Vue component trees bez memoization
Re-render drzewa > 100 komponentów po każdym state change. Brak React.memo / useMemo / useCallback sprawia, że każdy parent update propaguje w dół. Brak virtualizacji list (react-window, @tanstack/react-virtual) dla tabel > 50 wierszy. Classic React performance debt.
Optymalizacja INP
6a. Long task breakdown
// BAD: 200ms long task blokujący main threadfunction processItems(items) { return items.map(item => expensiveOperation(item));}
// GOOD: chunk work w 16ms framesasync function processItems(items) { const result = []; for (let i = 0; i < items.length; i++) { result.push(expensiveOperation(items[i])); if (i % 50 === 0) { // Yield to main thread — pozwala obsłużyć pending inputs await new Promise(resolve => setTimeout(resolve, 0)); } } return result;}6b. Async z scheduler.yield() (Chrome 129+)
async function processItems(items) { for (const item of items) { expensiveOperation(item); // Yield to main thread jeśli input pending — idiomatic Scheduling API if ('scheduler' in window && 'yield' in window.scheduler) { await window.scheduler.yield(); } }}scheduler.yield() jest lepszy niż setTimeout(0), bo browser yielduje tylko gdy coś czeka — nie ma unnecessary continuation cost. To natywny API, którego Chrome używa wewnętrznie.
6c. Defer non-critical updates (React 18+)
import { useDeferredValue, useTransition } from 'react';
function SearchResults({ query }: { query: string }) { // deferredQuery aktualizuje się w low-priority — nie blokuje typing const deferredQuery = useDeferredValue(query); const results = useSearchResults(deferredQuery); return <ResultsList results={results} />;}
function Filters({ onFilterChange }) { const [isPending, startTransition] = useTransition();
const handleFilter = (filter: Filter) => { startTransition(() => { // Ciężki state update — oznaczony jako non-urgent onFilterChange(filter); }); };
return <FilterUI disabled={isPending} onChange={handleFilter} />;}6d. Debounce user input
import { debounce } from 'lodash-es';
const handleSearch = debounce((query) => { setSearchResults(fetchResults(query));}, 300);Dla modern codebases bez lodash — wystarczy 10-line custom hook useDebouncedCallback.
6e. Reduce bundle size
- Tree-shake unused code (import named, nie default całego modułu)
- Code-split per route (Next.js
dynamic(), Astroclient:only, Reactlazy()) - Remove unnecessary polyfills (browserslist targets modern —
>= 0.5%, not dead, not IE 11) - Replace heavy libraries: moment → date-fns → native
Intl.DateTimeFormat, lodash → lodash-es lub native
6f. Use CSS instead of JS where possible
:hoverzamiastonMouseEnter+ React state- CSS
@starting-style+ transitions zamiast imperatywnych animacji <details>/<summary>zamiast JS-driven accordion- CSS
:has()zamiast JS conditional rendering dla prostych przypadków - View Transitions API zamiast
animate()dla route transitions
Case study — yote.pl INP
Metryki yote.pl (stan: kwiecień 2026, CrUX p75 mobile):
- INP: < 100ms
- LCP: < 1.8s
- CLS: < 0.05
- Wszystkie trzy good w CrUX — zielone w Search Console
Co o tym decyduje:
- Astro islands architecture — minimalne JS (tylko Alpine.js, ~15 KB gzipped dla interaktywnych komponentów)
- Inline critical CSS — zero render-blocking stylesheets w
<head> - No React/Vue runtime na stronie — zero main thread reconciliation, zero hydration cost
- Self-hosted fonts — zero external DNS lookups +
font-display: swap - Defer analytics — Ahrefs z
async defer, Turnstile ładowany tylko na/audyt-online(route-level code splitting)
Dla porównania: typowy WordPress + Elementor + 20 plugins + Google Fonts + 4 analytics = INP 300–700ms mobile. Dla większości stron to oznacza “needs improvement” lub “poor” w Search Console.
Rzecz, którą często pomijają: architektura stackowa ma większy wpływ na INP niż mikro-optymalizacje w kodzie. Przejście z SPA React na SSR Next.js lub Astro potrafi obniżyć INP z 400ms do 80ms bez jednej linii custom performance code. Wybór stacku w fazie planowania projektu jest silniejszą dźwignią niż późniejsze refactory.
INP vs SEO ranking
Google potwierdziło w maju 2023: INP jako Core Web Vital = direct ranking signal. Wpływ szacowany na ~5% ranking score (zgodnie z wypowiedziami Martin Splitt z Google Search Relations, 2024). Dla bardzo competitive niches (finance, SaaS, e-commerce fashion), różnica 200ms INP może oznaczać 2–5 pozycji w SERP — szczególnie gdy konkurenci mają podobny content relevance.
Jednak — nie jest to najważniejszy czynnik. Content relevance > E-E-A-T > Core Web Vitals > pozostałe technical SEO. INP funkcjonuje jako tie-breaker między podobnymi stronami — gdy Google ma 10 dobrych kandydatów na pozycję #1–3, CWV może przeważyć. Dla contentu unikalnego i mocnego — INP nie uratuje słabej strony, ale dobry INP usuwa jedną barierę.
Praktyczny efekt: strona z INP 150ms pobije stronę z INP 450ms przy równym contencie. Ale strona z lepszym contentem i INP 450ms pobije stronę z INP 100ms i słabszym contentem. Hierarchia się nie zmienia.
Pełna optymalizacja CWV to jeden z elementów naszego pakietu pozycjonowania SEO — bazujemy na stacku Astro + Cloudflare Workers, w którym INP poniżej 200ms jest baseline’em out-of-the-box. Jeśli Twoja strona ma problemy z CWV, zacznij od bezpłatnego audytu online — w 48h dostaniesz PDF z konkretnymi rekomendacjami + szacowany impact na ranking.
TL;DR + action items
- Mierz INP w CrUX via PageSpeed Insights — to jedyne źródło oficjalnego ranking signal.
- Target: < 200ms na 75. percentylu mobile.
- Top 3 fixes: break long tasks (scheduler.yield), defer non-critical updates (useDeferredValue), reduce bundle (code-split per route).
- Tools:
web-vitalslibrary dla RUM, Chrome DevTools Performance panel dla lab debugging, PageSpeed Insights dla weryfikacji. - Framework hint: Astro/Next.js SSR > React SPA dla content-heavy sites. Stack matters more than micro-optimizations.
- Debugging pattern:
web-vitalsattributionobject → konkretny DOM element + event type worst INP. Nie zgaduj, mierz.
Sources
- web.dev — INP guide — oficjalna dokumentacja Google
- Google Search Central — Core Web Vitals — ranking signal docs
- CrUX Dashboard — real-field data source
- web-vitals NPM — RUM library by Google
- web.dev — INP deep dive — technical background z rationale (Google skonsolidowało content z developer.chrome.com)
- Scheduler API — scheduler.yield() — natywne yielding do main thread
Najczęstsze pytania
Kiedy INP zastąpiło FID?
Google ogłosił INP jako Core Web Vital w maju 2023, a oficjalnie zastąpił FID 12 marca 2024. Od tej daty FID został usunięty z raportów CrUX, PageSpeed Insights i Search Console.
Jaki jest dobry wynik INP?
Dobry INP to poniżej 200ms na 75. percentylu urządzeń mobilnych. 200–500ms oznacza, że strona wymaga poprawy, a powyżej 500ms wynik jest słaby i wpływa negatywnie na ranking w Google.
Jak zmierzyć INP na własnej stronie?
Trzy źródła: PageSpeed Insights (CrUX, real-field), Chrome DevTools Performance panel (lab) oraz biblioteka web-vitals via PerformanceObserver dla RUM. Oficjalnym sygnałem rankingowym jest tylko CrUX.
Czym INP różni się od FID?
FID mierzył tylko opóźnienie pierwszej interakcji (input delay). INP mierzy cały cykl każdej interakcji — input delay + processing time + presentation delay — i wybiera najgorszą w sesji (nie średnią).
Czy INP wpływa na pozycje w Google?
Tak. INP jako Core Web Vital jest bezpośrednim sygnałem rankingowym od 12 marca 2024. Wpływ szacowany jest na ~5% ranking score, szczególnie w konkurencyjnych niszach (finanse, SaaS).
Jak optymalizować INP w React/Next.js?
Kluczowe techniki: rozbijanie długich tasków (scheduler.yield, setTimeout), useDeferredValue i useTransition dla non-critical updates, debouncing user input, React.memo dla ciężkich komponentów oraz code-splitting per route.
Potrzebujesz pomocy z SEO?
Bezpłatny audyt Twojej strony — PDF z rekomendacjami w 48h.
Chcę bezpłatny audyt →