StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • Audyt SEO i Performance
  • Testy automatyczne i QA
  • Konsultacje Produktowe
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
ReactSEOWydajność

INP w React. 5 wzorców, które niszczą Interaction to Next Paint i jak je naprawić kodem

INP powyżej 200 ms w React? Winowajcą jest zazwyczaj jeden z tych pięciu wzorców. Dowiedz się, jak je zidentyfikować i naprawić z inżynieryjną precyzją.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
30 maja 2026 08:00
Czytanie
6 min czytania
Aktualizacja
4 czerwca 2026 13:00

ostatecznie odesłał na emeryturę, wymuszając zupełnie nowe spojrzenie na responsywność. Metryka ocenia teraz najgorszą interakcję z całej sesji, bez taryfy ulgowej dla aplikacji React wypełnionych bogatym stanem, potężnymi listami i rozbudowanymi zdarzeniami. Jest jednak kilka powtarzalnych wzorców, które radzą sobie z większością problemów z optymalizacją, z których każdy posiada gotowe rozwiązanie systemowe.

w skrócie

  • mierzy całą sesję, a nie tylko pierwsze kliknięcie. Metryka bezlitośnie ocenia input delay, czas przetwarzania w handlerze oraz czas niezbędny do odrysowania interfejsu.
  • Zbędne re-rendery to najczęstszy winowajca opóźnień. Każde kliknięcie przerenderowujące połowę drzewa DOM brutalnie wydłuża czas reakcji aplikacji. Memoizacja i rygorystyczne cięcie kontekstu dają tu fenomenalne rezultaty.
  • Ciężkie zadania synchroniczne całkowicie blokują główny wątek. Skomplikowane sortowanie, filtrowanie czy parsowanie w event handlerze drastycznie zamraża przeglądarkę. Przenieś takie operacje w tło, wykorzystując potęgę useTransition.
  • Duże listy bezwzględnie renderuj wirtualnie. Tysiąc wierszy w strukturze DOM to tysiąc węzłów do uciążliwego przeliczenia przy najdrobniejszym ruchu. Wirtualizacja bezbłędnie odciąża system, renderując wyłącznie to, co widać aktualnie na ekranie.
  • Świadomie zarządzaj priorytetami aktualizacji stanu. Nowoczesne hooki useTransition i useDeferredValue pozwalają inteligentnie odłożyć w czasie mniej krytyczną pracę i zagwarantować natychmiastową odpowiedź wizualną.
  • Opieraj optymalizację wyłącznie na twardych danych od realnych użytkowników. INP pozostaje metryką w oparciu o dane z prawdziwych sesji użytkowników, dlatego całą prawdę o wydajności ujawni Ci dopiero Google Search Console w duecie z biblioteką web-vitals.

Dlaczego INP boli właśnie aplikacje React

Aby precyzyjnie podejść do problemu, musimy rozłożyć INP na podstawowe części. Każda interakcja przechodzi przez trzy krytyczne etapy, które bezpośrednio decydują o odczuciach użytkownika. Pierwszym z nich jest , określający czas niezbędny na podjęcie obsługi zdarzenia przez przeglądarkę. Następnie do akcji wkracza faza przetwarzania napędzana przez event handler oraz wynikający z niego render. Całość zamyka , który ostatecznie weryfikuje opóźnienie przed wyświetleniem zmodyfikowanego interfejsu. Nowa metryka Google bezwzględnie rejestruje najgorszy z tych incydentów na przestrzeni całej sesji klienta.

W potężnych aplikacjach zbudowanych w React każdy ze wspomnianych etapów potrafi skutecznie spowolnić działanie produktu. Główny wątek notorycznie dusi się podczas procesu hydratacji lub przy przeciągającym się renderowaniu. Sam event handler niemal nieustannie wyzwala kaskadowe re-rendery i zmusza algorytmy do przeliczania bardzo dużych ilości informacji bez żadnego sensownego uzasadnienia. Masywne drzewo komponentów z łatwością powstrzymuje ostateczny krok, w którym przeglądarka w końcu rysuje nowy widok. Wszystkie prezentowane dziś wzorce bezbłędnie potwierdzają absolutnie jedną tezę. Każda interakcja użytkownika uruchamia istną lawinę operacji, z którymi silnik systemu po prostu nie potrafi się uporać przed wygenerowaniem kolejnej klatki.

Wzorzec 1: Re-render całego drzewa przy każdej interakcji

Najczęstszym błędem obciążającym procesory jest osadzenie stanu na zbyt wysokim poziomie architektury. Przypadkowe kliknięcie lub wprowadzenie chociażby jednego znaku w formularzu natychmiast inicjuje przebudowę tych komponentów, które kompletnie nie wykorzystują tej zmodyfikowanej wartości.

Code
// ŹLE: jeden stan steruje wszystkim, a każda zmiana wymusza re-render całej listy
function Dashboard() {
  const [search, setSearch] = useState('')
  const [items] = useState(() => generateItems(2000))
 
  return (
    <div>
      <input value={search} onChange={(e) => setSearch(e.target.value)} />
      {/* Ta lista przelicza się przy każdym znaku w search, choć od niego nie zależy */}
      <ExpensiveList items={items} />
    </div>
  )
}

Tutaj każde naciśnięcie klawisza w polu wyszukiwania zmusza Reacta do ponownego renderu ExpensiveList, mimo że lista nie korzysta z wartości search. Przy dwóch tysiącach elementów czas przetwarzania interakcji rośnie do poziomu, który INP bezględnie wyłapie.

Rozwiązanie zaczyna się od memoizacji komponentu, który nie powinien reagować na zmianę:

Code
// DOBRZE — ExpensiveList nie zależy od search, więc go izolujemy
const ExpensiveList = memo(function ExpensiveList({
  items,
}: {
  items: Item[]
}) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
})

Dzięki memo React pomija ponowny render listy, dopóki jej propsy się nie zmienią. W świeżym projekcie React Compiler zrobi część tej pracy automatycznie, ale nadal musisz rozumieć granice renderowania. Kompilator nie naprawi źle ustawionego stanu.

Info

Zanim sięgniesz po memoizację, zadaj pytanie, czy stan jest na właściwym poziomie. Często najczystszym rozwiązaniem nie jest memo, lecz przeniesienie stanu wyszukiwania niżej, czyli bliżej komponentu, który faktycznie go używa. Mniej współdzielonego stanu to mniej kaskadowych re-renderów.

Wzorzec 2: Ciężkie zadanie synchroniczne w event handlerze

Drugi wzorzec to klasyk: użytkownik klika, a w odpowiedzi odpala się sortowanie, filtrowanie albo transformacja kilku tysięcy rekordów — wszystko synchronicznie, w jednym bloku.

Code
// ŹLE — sortowanie 10 000 rekordów blokuje główny wątek przed odrysowaniem
function ProductTable({ products }: { products: Product[] }) {
  const [sorted, setSorted] = useState(products)
 
  function handleSort(key: keyof Product) {
    const result = [...products].sort((a, b) => heavyCompare(a, b, key))
    setSorted(result)
  }
 
  return <Table data={sorted} onSort={handleSort} />
}

Problem polega na tym, że heavyCompare na dużym zbiorze potrafi zająć główny wątek na kilkaset milisekund. Przez ten czas przeglądarka nie odrysuje niczego, czyli ani efektu kliknięcia, ani nawet wskaźnika ładowania. INP zmierzy całe to opóźnienie jako jedną fatalną interakcję.

React 18 dał na to czytelne narzędzie. Dzięki useTransition możemy oznaczyć aktualizację jako i dzięki czemu przeglądarka najpierw zareaguje na kliknięcie, a dopiero potem dokończy ciężką pracę:

Code
// DOBRZE — useTransition oddaje pierwszeństwo odrysowaniu, sortowanie idzie w tle
function ProductTable({ products }: { products: Product[] }) {
  const [sorted, setSorted] = useState(products)
  const [isPending, startTransition] = useTransition()
 
  function handleSort(key: keyof Product) {
    startTransition(() => {
      const result = [...products].sort((a, b) => heavyCompare(a, b, key))
      setSorted(result)
    })
  }
 
  return (
    <>
      {isPending && <SortingIndicator />}
      <Table data={sorted} onSort={handleSort} />
    </>
  )
}

Interakcja staje się responsywna, ponieważ użytkownik od razu widzi reakcję, czyli wskaźnik sortowania zamiast zamrożonego interfejsu. Jeśli zadanie jest naprawdę ciężkie i czysto obliczeniowe, przenieś je do . Wtedy w ogóle zdejmujesz je z głównego wątku.

Wzorzec 3: Renderowanie tysięcy elementów naraz

Trzeci wzorzec dotyczy długich list i tabel. Nawet jeśli każdy wiersz jest lekki, sama ich liczba sprawia, że przeglądarka musi utrzymać i przeliczyć ogromne drzewo DOM. Przy każdej interakcji — przewinięciu, rozwinięciu, filtrze — ten ciężar wraca.

Code
// ŹLE — 5000 wierszy w DOM, każda interakcja przelicza całość
function LogViewer({ logs }: { logs: LogEntry[] }) {
  return (
    <div className="h-[600px] overflow-auto">
      {logs.map((log) => (
        <LogRow key={log.id} entry={log} />
      ))}
    </div>
  )
}

Odpowiedzią jest , czyli renderujemy tylko te wiersze, które faktycznie są w widocznym oknie, a resztę zastępujemy odpowiednio wysokim pustym obszarem. Najwygodniej zrobić to dziś biblioteką TanStack Virtual:

Code
// DOBRZE — renderujemy tylko widoczne wiersze
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
 
function LogViewer({ logs }: { logs: LogEntry[] }) {
  const parentRef = useRef<HTMLDivElement>(null)
 
  const virtualizer = useVirtualizer({
    count: logs.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 48,
    overscan: 10,
  })
 
  return (
    <div ref={parentRef} className="h-[600px] overflow-auto">
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((row) => (
          <div
            key={logs[row.index].id}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${row.start}px)`,
              width: '100%',
            }}
          >
            <LogRow entry={logs[row.index]} />
          </div>
        ))}
      </div>
    </div>
  )
}

Niezależnie od tego, czy danych jest pięćset czy pięćdziesiąt tysięcy, w DOM żyje zaledwie kilkanaście węzłów. Interakcje przestają zależeć od rozmiaru zbioru, a to dokładnie ta zależność, która rozsadzała INP. Warto przy tym pamiętać, że wirtualizacja nie zawsze jest konieczna, ponieważ przy umiarkowanych listach często wystarczy zwykła paginacja, która w ogóle nie wpuszcza nadmiaru danych do DOM.

Wzorzec 4: Stan w jednym wielkim kontekście

Czwarty wzorzec jest bardziej architektoniczny. Kiedy cały stan aplikacji ląduje w pojedynczym kontekście React, każda jego zmiana powiadamia wszystkich konsumentów — także te komponenty, którym zmieniła się jedna, zupełnie obojętna dla nich wartość.

Code
// ŹLE — jeden kontekst na wszystko, zmiana theme przerenderowuje konsumentów cart
const AppContext = createContext<AppState | null>(null)
 
function AppProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('light')
  const [cart, setCart] = useState<CartItem[]>([])
  const [user, setUser] = useState<User | null>(null)
 
  const value = { theme, setTheme, cart, setCart, user, setUser }
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>
}

Przełączenie motywu nie powinno mieć żadnego wpływu na komponent koszyka, a jednak ma — bo obiekt value powstaje na nowo przy każdym renderze providera, więc React traktuje go jako zmieniony dla wszystkich. Najprostsze i najtrwalsze rozwiązanie to rozdzielenie kontekstów według tego, co faktycznie zmienia się razem:

Code
// DOBRZE — osobne konteksty, zmiana jednego nie dotyka konsumentów drugiego
const ThemeContext = createContext<ThemeState | null>(null)
const CartContext = createContext<CartState | null>(null)
const UserContext = createContext<UserState | null>(null)

Konsument koszyka subskrybuje teraz wyłącznie CartContext i pozostaje obojętny na zmiany motywu czy danych użytkownika. Jeśli stan jest naprawdę złożony, alternatywą jest sięgnięcie po bibliotekę z selektorami, jak Zustand, gdzie komponent przerenderowuje się tylko wtedy, gdy zmieni się konkretny wycinek stanu, który czyta.

Wzorzec 5: Synchroniczna walidacja i formatowanie przy każdym keystroke

Ostatni wzorzec to formularze, które przy każdym wciśniętym klawiszu uruchamiają kosztowną walidację, formatowanie albo przeliczenie zależnych pól.

Code
// ŹLE — pełna walidacja schematu przy każdym znaku
function CheckoutForm() {
  const [values, setValues] = useState(initialValues)
 
  function handleChange(field: string, value: string) {
    const next = { ...values, [field]: value }
    setValues(next)
    // Walidacja całego formularza na każdy keystroke — zbędna i kosztowna
    validateEntireSchema(next)
  }
 
  return <Form values={values} onChange={handleChange} />
}

Walidacja całego schematu przy każdym znaku to praca, której użytkownik w danym momencie wcale nie potrzebuje — liczy się dopiero przy opuszczeniu pola albo próbie wysłania. Tutaj naturalnym narzędziem jest useDeferredValue, który pozwala odłożyć kosztowną reakcję na zmianę, zachowując natychmiastową responsywność samego pola:

Code
// DOBRZE — pole reaguje natychmiast, walidacja podąża z opóźnieniem
function CheckoutForm() {
  const [values, setValues] = useState(initialValues)
  const deferredValues = useDeferredValue(values)
 
  const errors = useMemo(
    () => validateEntireSchema(deferredValues),
    [deferredValues],
  )
 
  return <Form values={values} errors={errors} onChange={setValues} />
}

Pole formularza aktualizuje się od razu, ponieważ korzysta z values, podczas gdy ciężka walidacja pracuje na deferredValues i nadąża w tempie, które nie blokuje wpisywania. W praktyce jeszcze lepiej połączyć to z biblioteką taką jak React Hook Form, która domyślnie waliduje przy zdarzeniach typu blur albo submit, a nie na każdy znak — o całym stacku formularzowym pisałem w osobnym artykule o React Hook Form i Zod.

Jak namierzyć, który wzorzec psuje Twój INP

W realnym projekcie nie zaczynaj od strzelania na ślepu i zacznij od danych terenowych, bo INP jest metryką sesyjną i tylko realni użytkownicy klikają w miejsca, które są problematyczne. Search Console pokaże adresy z problemem, a Googleweb-vitals podepniesz pod własną analitykę i zbierzesz atrybucję: konkretny element, typ interakcji, czas.

Gdy już wiesz, gdzie szukać, panel Performance w DevTools i sekcja Interactions pozwolą zobaczyć rozbicie pojedynczej interakcji na input delay, przetwarzanie i prezentację. To rozbicie od razu kieruje Cię do właściwego wzorca: długi input delay sugeruje zajęty główny wątek, długie przetwarzanie wskazuje na ciężki handler albo kaskadę re-renderów, a długa prezentacja zdradza zbyt duże drzewo DOM.

Werdykt Labu

INP obnaża koszt codziennych decyzji w React: stan za wysoko, lista bez wirtualizacji, handler robiący za dużo, kontekst jako worek na wszystko. Każdy z tych wzorców sprowadza się do wspólnego mianownika: interakcja uruchamia więcej pracy, niż przeglądarka zdąży wykonać przed odrysowaniem.

Nie naprawiaj tego wszystkiego waląc z armaty, tylko najpierw zmierz, który etap interakcji jest najgorszy: input delay, przetwarzanie czy presentation delay. Dopiero potem wybierz narzędzie memoizacja i podział stanu tną re-rendery, podczas gdy useTransition i useDeferredValue oddają pierwszeństwo pilnemu UI. Wirtualizacja odcina koszt wielkich list.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
SEO & Performance
  • Dlaczego INP boli właśnie aplikacje React1 min
  • Wzorzec 1: Re-render całego drzewa przy każdej interakcji1 min
  • Wzorzec 2: Ciężkie zadanie synchroniczne w event handlerze1 min
  • Wzorzec 3: Renderowanie tysięcy elementów naraz1 min
  • Wzorzec 4: Stan w jednym wielkim kontekście1 min
  • Wzorzec 5: Synchroniczna walidacja i formatowanie przy każdym keystroke1 min
  • Jak namierzyć, który wzorzec psuje Twój INP1 min
  • Werdykt Labu1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 30 maja 2026

Definicję metryki, progi i techniki optymalizacji zweryfikowano na podstawie oficjalnej dokumentacji web.dev i React:

web.dev: Interaction to Next Paint (INP), web.dev: Optimize INP, React docs: useTransition, React docs: useDeferredValue, TanStack Virtual.

Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Core Web Vitals — jak przyspieszyć stronę i poprawić pozycję w Google
Core Web Vitals — jak przyspieszyć stronę i poprawić pozycję w Google

LCP, INP i CLS — co każda metryka mierzy, jak ją poprawić i co naprawdę wpływa na pozycje w Google. Bez ogólnych rad, konkretne techniki.

Maciej Sala

Maciej Sala

Founder Strivelab

14 listopada 2025
TypeScript w React bez bólu — 7 wzorców, które realnie robią różnicę
TypeScript w React bez bólu — 7 wzorców, które realnie robią różnicę

7 wzorców TypeScript, które faktycznie używasz w produkcji — nie w tutorialach. discriminated unions, generics, satisfies i polymorphic components.

Maciej Sala

Maciej Sala

Founder Strivelab

24 kwietnia 2026
React 19 Actions — formularz bez onSubmit, useOptimistic i useActionState w praktyce
React 19 Actions — formularz bez onSubmit, useOptimistic i useActionState w praktyce

Koniec z onSubmit i ręcznym stanem ładowania — React 19 Actions przepisują formularze od fundamentów. Migracja bez bólu głowy.

Maciej Sala

Maciej Sala

Founder Strivelab

24 kwietnia 2026

Poprzedni wpis

WordPress, Astro czy Next.js? Matryca decyzyjna, zanim ruszysz z migracją
WordPress, Astro czy Next.js? Matryca decyzyjna, zanim ruszysz z migracją

WordPress, Astro czy Next.js? Pięć zmiennych, które rozstrzygną to za Ciebie, zanim przepiszesz jedną linię kodu.

Maciej Sala

Maciej Sala

Founder Strivelab

29 maja 2026

Następny wpis

Hydration Mismatch a SEO: co widzi Googlebot? Next.js pod lupą
Hydration Mismatch a SEO: co widzi Googlebot? Next.js pod lupą

Googlebot widzi serwerowy HTML, a nie stan po hydracji. Gdy Next.js się "rozjeżdża", SEO cierpi. Dowiedz się, co naprawdę indeksuje Google i jak to naprawić.

Maciej Sala

Maciej Sala

Founder Strivelab

30 maja 2026