INP optimizacija u 2026: Praktični vodič za brži i odzivniji web

Praktični vodič za optimizaciju INP-a (Interaction to Next Paint) — Core Web Vital metrike odzivnosti. Tehnike poput scheduler.yield(), LoAF API dijagnostike, Web Workers, upravljanja skriptama trećih strana i framework optimizacija za React i Vue.

Uvod: Zašto je INP najvažnija metrika za odzivnost vašeg weba

Zamislite ovo: korisnik klikne na gumb „Dodaj u košaricu", ali ništa se ne događa. Prođe sekunda, pa još jedna. Korisnik klikne ponovno — i sad mu se u košarici pojave dva proizvoda umjesto jednog. Frustracija je očita, a krivac ima ime: Interaction to Next Paint (INP).

INP je Core Web Vital metrika koja mjeri koliko brzo vaša web stranica vizualno reagira na korisničke interakcije — klikove, dodire na zaslonu i pritiske tipki. Za razliku od svog prethodnika First Input Delay (FID), koji je mjerio samo kašnjenje prvog unosa, INP prati svaku interakciju kroz cijelu sesiju i prijavljuje onu najgoru (ili gotovo najgoru).

A brojke iz siječnja 2026. nisu lijepe: 43% web stranica ne prolazi INP prag od 200 ms. To INP čini najčešćim razlogom pada na Core Web Vitals testu. Mobilni uređaji bilježe 60–80% lošije INP rezultate od desktopa, a kad znamo da 68% ukupnog web prometa dolazi s mobitela — ignorirati ovo jednostavno nije opcija.

Ajmo onda redom. U ovom vodiču prođemo sve što trebate znati o INP-u: od triju faza svake interakcije, preko konkretnih tehnika optimizacije s radnim primjerima koda, do modernih alata za dijagnostiku poput Long Animation Frames (LoAF) API-ja. Cilj je jasan — da nakon čitanja možete sustavno pronaći i eliminirati svaki uzrok sporog INP-a na vašim stranicama.

Što je INP i kako se razlikuje od FID-a

Interaction to Next Paint mjeri ukupno vrijeme od trenutka kada korisnik pokrene interakciju do trenutka kada preglednik prikaže vizualnu promjenu na zaslonu. Zvuči jednostavno, ali ispod haube se kriju tri različite faze — i razumijevanje svake od njih ključno je za uspješnu optimizaciju.

Tri faze INP-a

Svaka interakcija prolazi kroz tri faze:

  1. Input Delay (kašnjenje unosa) — vrijeme od korisničke akcije do početka izvršavanja event handlera. Ovo kašnjenje nastaje kada je glavna nit preglednika zauzeta drugim poslom — parsiranjem JavaScripta, izvršavanjem prethodno zakazanih zadataka ili obradom drugih callback funkcija.
  2. Processing Time (vrijeme obrade) — vrijeme potrebno za izvršavanje svih event handlera povezanih s tom interakcijom. Ako imate višestruke event listenere na istom elementu, svi se zbrajaju.
  3. Presentation Delay (kašnjenje prikaza) — vrijeme od završetka event handlera do trenutka kada preglednik prikaže sljedeći frame. To uključuje izračun stilova, layout, paint i compositing.

INP je zbroj sve tri faze. I evo jednog zanimljivog podatka koji me osobno iznenadio: interakcije tijekom učitavanja stranice imaju p75 INP od 132 ms, dok interakcije nakon učitavanja imaju samo 50 ms — razlika od 2,6 puta! To znači da je upravo faza učitavanja najkritičnija za optimizaciju input delay-a.

FID vs. INP: Ključne razlike

FID je mjerio samo input delay prve interakcije na stranici. Praktično, to je značilo da je stranica mogla proći FID test, a korisnik i dalje imati užasno iskustvo nakon prvog klika.

Koliko je to bilo problematično? Prema podacima Chrome UX Reporta, 31% stranica koje su prolazile FID sada pada na INP testu. Gotovo trećina.

INP je puno poštenija metrika — mjeri cijelu sesiju, uključuje sve tri faze interakcije i prijavljuje stvarno korisničko iskustvo. Ne samo prvi dojam.

INP pragovi

  • Dobro: ≤ 200 ms — korisnik doživljava stranicu kao trenutno odzivnu
  • Potrebno poboljšanje: 200–500 ms — primjetno kašnjenje koje frustrira
  • Loše: > 500 ms — stranica se doima „zaglavljena"

Mjerenje se vrši na 75. percentilu, što znači da 75% vaših korisnika mora imati INP ispod praga da biste prošli. Dakle, ne možete se izvući samo zato što vam desktop korisnici imaju dobar rezultat — mobilni korisnici vas mogu potopiti.

Kako izmjeriti INP na vašoj stranici

Prije nego što bilo što počnete optimizirati, morate znati gdje stojite. INP se primarno mjeri pomoću podataka stvarnih korisnika (field data), ali laboratorijski alati vam isto mogu pomoći pri dijagnostici.

Field data alati (podaci stvarnih korisnika)

  • Google Search Console — u odjeljku Core Web Vitals vidjet ćete INP rezultate grupirane po URL obrascima. Ovo je vaša polazna točka — ako vidite crvene ili žute oznake za INP, imate problem koji treba rješavati.
  • PageSpeed Insights — unesite URL i pogledajte odjeljak „Field Data". INP se prikazuje s konkretnom vrijednošću u milisekundama.
  • Chrome UX Report (CrUX) — pristupite putem BigQuery-ja ili CrUX API-ja za detaljne podatke po URL-u. Malo je kompleksnije za setup, ali daje najdublje uvide.
  • web-vitals.js biblioteka — dodajte u svoju stranicu za RUM praćenje s atribucijskim podacima. Iskreno, ovo je moj omiljeni pristup jer vam daje granularnost koju nijedan drugi alat ne može.

Lab data alati (dijagnostika)

  • Chrome DevTools Performance panel — snimite sesiju dok klikćete po stranici. Tražite crvene oznake Long Tasks i provjerite Timing breakdown svake interakcije.
  • Lighthouse — od verzije 10+ prikazuje TBT (Total Blocking Time) koji služi kao solidan laboratorijski proxy za INP.
  • Web Vitals Chrome Extension — prikazuje INP u stvarnom vremenu dok koristite stranicu, uključujući LoAF podatke za svaku interakciju. Odličan za brzu provjeru dok razvijate.

Dijagnostika s Long Animation Frames (LoAF) API-jem

Okej, sad znate da imate INP problem. Ali zašto ga imate? Tu na scenu stupa Long Animation Frames (LoAF) API — moderni nasljednik Long Tasks API-ja koji pruža detaljne informacije o tome koje skripte blokiraju renderiranje.

LoAF bilježi svaki frame koji traje duže od 50 ms i pruža atribuciju na razini pojedinačnih skripti — uključujući URL skripte, funkciju koja je izvršena i koliko je trajala svaka faza. Evo kako ga koristite u praksi:

// Praćenje Long Animation Frames koji utječu na interakcije
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Filtriraj samo frameove duže od 100ms
    if (entry.duration > 100) {
      console.log('LoAF trajanje:', entry.duration, 'ms');

      // Pregledaj svaku skriptu u frameu
      for (const script of entry.scripts) {
        console.log('Skripta:', script.sourceURL);
        console.log('Funkcija:', script.sourceFunctionName);
        console.log('Trajanje izvršavanja:', script.executionStart
          ? entry.startTime + entry.duration - script.executionStart
          : 'N/A');
        console.log('Tip:', script.invokerType);
      }
    }
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });

Od verzije 4, web-vitals.js biblioteka automatski uključuje LoAF podatke u atribuciju INP-a kroz svojstvo longAnimationFrameEntries. Što znači da te podatke možete slati u svoj RUM sustav i analizirati ih retroaktivno — bez da ručno postavljate PerformanceObserver.

LoAF API trenutno podržava Chrome 123+, a Firefox je pokazao pozitivan stav prema implementaciji. Za preglednike bez podrške, graceful degradation je automatski — jednostavno nećete dobiti dodatne podatke, ali ništa se neće pokvariti.

Tehnika 1: Razbijanje dugih zadataka na glavnoj niti

Najčešći uzrok lošeg INP-a su dugi zadaci (long tasks) — bilo koji JavaScript zadatak koji blokira glavnu nit duže od 50 ms. Dok se takav zadatak izvršava, preglednik doslovno ne može obraditi korisničke interakcije. Korisnik klikne, a ništa se ne dogodi — jer je preglednik zauzet nečim drugim.

scheduler.yield() — moderni pristup

scheduler.yield() je relativno novi browser API koji rješava fundamentalni problem: omogućuje vam da „ustupite" kontrolu glavnoj niti usred dugog zadatka, dopuštajući pregledniku da obradi korisničke interakcije, a zatim nastavite rad tamo gdje ste stali.

I evo ključne razlike naspram starog pristupa s setTimeout(): scheduler.yield() stavlja nastavak vašeg zadatka na početak reda čekanja, dok setTimeout() šalje zadatak na kraj reda. Praktično, vaš posao neće biti odgođen manje važnim zadacima koji su se uguali u međuvremenu.

// LOŠE: Jedan dugi zadatak koji blokira 300ms
function processAllItems(items) {
  for (const item of items) {
    processItem(item);  // Svaki item traje ~5ms
    updateDOM(item);    // Svaki DOM update ~3ms
  }
  // Ukupno: 50 itema × 8ms = 400ms blokade!
}

// DOBRO: Razbijanje uz scheduler.yield()
async function processAllItems(items) {
  for (const item of items) {
    processItem(item);
    updateDOM(item);

    // Prepusti kontrolu pregledniku svakih ~40ms
    if (performance.now() - lastYield > 40) {
      await scheduler.yield();
      lastYield = performance.now();
    }
  }
}

// NAJBOLJE: S graceful degradation za starije preglednike
async function processAllItems(items) {
  let lastYield = performance.now();

  for (const item of items) {
    processItem(item);
    updateDOM(item);

    if (performance.now() - lastYield > 40) {
      // Koristi scheduler.yield() ako je dostupan, inače nastavi
      await globalThis.scheduler?.yield?.();
      lastYield = performance.now();
    }
  }
}

Praktični primjer: Filtriranje velike liste

Zamislite e-commerce stranicu s 500 proizvoda i filterom po kategoriji. Bez optimizacije, klik na filter blokira glavnu nit dok se svi proizvodi ne prefiltriraju i rerenderiraju. Korisnik doslovno vidi zamrznuti ekran.

// Optimizirani pristup s chunked procesiranjem
async function filterProducts(products, category) {
  const CHUNK_SIZE = 20;
  const filtered = [];
  const container = document.getElementById('product-grid');

  // Očisti grid odmah — brz vizualni feedback
  container.innerHTML = '<div class="loading-spinner"></div>';

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

    for (const product of chunk) {
      if (product.category === category) {
        filtered.push(product);
      }
    }

    // Yield nakon svakog chunka
    await globalThis.scheduler?.yield?.();
  }

  // Renderiranje u jednom batchu nakon filtriranja
  requestAnimationFrame(() => {
    container.innerHTML = filtered
      .map(p => `<div class="product-card">${p.name}</div>`)
      .join('');
  });
}

Tehnika 2: Optimizacija event handlera

Druga faza INP-a — processing time — izravno ovisi o tome koliko posla radite unutar event handlera. Kroz praksu sam se uvjerio da se većina INP problema može svesti na tri zlatna pravila.

Pravilo 1: Razdvojite primarni od sekundarnog posla

Ovo je možda najjednostavnija, a najefektnija promjena. Ideja je da u event handleru napravite samo ono što korisnik očekuje da se dogodi — a sve ostalo prebacite u pozadinu.

// LOŠE: Sve u jednom event handleru
button.addEventListener('click', () => {
  // Primarno: otvori izbornik (ono što korisnik očekuje)
  openMenu();

  // Sekundarno: analitika, logiranje, prefetch
  trackAnalytics('menu_opened');
  prefetchMenuContent();
  logUserBehavior('menu_click');
});

// DOBRO: Sekundarni posao u zasebnom zadatku
button.addEventListener('click', () => {
  // Primarno: odmah otvori izbornik
  openMenu();

  // Sekundarni posao — izvan ove interakcije
  requestIdleCallback(() => {
    trackAnalytics('menu_opened');
    prefetchMenuContent();
    logUserBehavior('menu_click');
  });
});

Pravilo 2: Koristite framework-specifične optimizacije

Moderni frameworkovi (i React i Vue) imaju ugrađene mehanizme za odgodu manje hitnog posla. Šteta je ne koristiti ih:

// React: useTransition za ne-hitne UI updateove
import { useTransition, useState } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleSearch(e) {
    const value = e.target.value;
    setQuery(value);  // Hitno: odmah ažuriraj input polje

    startTransition(() => {
      // Ne-hitno: pretraživanje može čekati
      const filtered = searchProducts(value);
      setResults(filtered);
    });
  }

  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending ? <Spinner /> : <ResultList items={results} />}
    </div>
  );
}

// Vue 3: computed + shallowRef za optimizirano renderiranje
import { ref, computed, shallowRef } from 'vue';

const products = shallowRef(allProducts);
const category = ref('all');

const filteredProducts = computed(() => {
  if (category.value === 'all') return products.value;
  return products.value.filter(p => p.category === category.value);
});

Pravilo 3: Smanjite DOM složenost

Presentation delay — treća faza INP-a — izravno ovisi o veličini i složenosti DOM-a. Svaka interakcija koja mijenja DOM pokreće izračun stilova i layout. Veći DOM znači sporije izračune. Toliko je jednostavno (i toliko se često zanemaruje).

/* CSS containment: recite pregledniku da izolira dijelove stranice */
.sidebar {
  contain: strict;
  /* Promjene u sidebaru neće utjecati na layout ostatka stranice */
}

.product-card {
  contain: content;
  /* Svaka kartica se renderira neovisno */
}

/* content-visibility za off-screen sadržaj */
.comments-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
  /* Preglednik preskače renderiranje dok sekcija nije vidljiva */
}

Tehnika 3: Upravljanje skriptama trećih strana

Evo nečeg što puno programera ne želi čuti: skripte trećih strana su često najčešći krivac za loš INP. „Ali to nije moj kod!" — znam, ali korisnika to ne zanima. Prema podacima iz 2026., najgori krivci su:

  • Chat widgeti — većina live-chat skripti agresivno zauzima glavnu nit, čak i kad korisnik uopće ne koristi chat
  • Analitički alati — posebno kad se nakupe višestruki servisi (Google Analytics + Facebook Pixel + HotJar + Clarity + ... dobijete ideju)
  • Automatski slideri — tranzicije koje se okidaju svake 3 sekunde mogu se preklopiti s korisničkim klikovima i stvoriti input delay
  • Teški page builderi — Divi i Elementor dodaju duboko ugniježđeni DOM koji usporava layout izračune

Strategija upravljanja

Rješenje je zapravo prilično elegantno — ne učitavajte skripte odmah, nego ih odgodite dok korisnik ne pokaže namjeru interakcije ili dok ne prođe dovoljno vremena:

<!-- LOŠE: Sve skripte se učitavaju odmah -->
<script src="https://chat-widget.com/loader.js"></script>
<script src="https://analytics.com/track.js"></script>

<!-- DOBRO: Odgodite ne-kritične skripte -->
<script>
  // Chat widget tek nakon 5 sekundi ili korisničke interakcije
  function loadChatWidget() {
    const script = document.createElement('script');
    script.src = 'https://chat-widget.com/loader.js';
    document.body.appendChild(script);
  }

  // Učitaj na user interaction ili nakon timeouta
  const events = ['scroll', 'click', 'touchstart', 'keydown'];
  let loaded = false;

  function triggerLoad() {
    if (loaded) return;
    loaded = true;
    events.forEach(e => window.removeEventListener(e, triggerLoad));
    loadChatWidget();
  }

  events.forEach(e => window.addEventListener(e, triggerLoad, { once: true }));
  setTimeout(triggerLoad, 5000);
</script>

Tehnika 4: Web Workers za teške izračune

Ponekad imate posao koji jednostavno ne možete (ili ne želite) razbiti na manje dijelove — kriptografija, složeno sortiranje, obrada velikih datasetova. Za takve situacije, Web Workers su pravo rješenje. Oni izvršavaju JavaScript u zasebnoj niti, potpuno oslobađajući glavnu nit za korisničke interakcije.

// worker.js — izvršava se u zasebnoj niti
self.addEventListener('message', (event) => {
  const { products, sortBy, filters } = event.data;

  // Teški posao: filtriranje i sortiranje 10.000 proizvoda
  let result = products;

  if (filters.category) {
    result = result.filter(p => p.category === filters.category);
  }
  if (filters.priceRange) {
    result = result.filter(p =>
      p.price >= filters.priceRange[0] &&
      p.price <= filters.priceRange[1]
    );
  }

  result.sort((a, b) => {
    if (sortBy === 'price-asc') return a.price - b.price;
    if (sortBy === 'price-desc') return b.price - a.price;
    if (sortBy === 'name') return a.name.localeCompare(b.name, 'hr');
    return 0;
  });

  self.postMessage({ filteredProducts: result });
});

// main.js — korištenje workera
const worker = new Worker('/worker.js');

filterButton.addEventListener('click', () => {
  // Odmah pokažite loading stanje (brz vizualni feedback)
  showLoadingSpinner();

  // Delegirajte teški posao workeru
  worker.postMessage({
    products: allProducts,
    sortBy: currentSort,
    filters: currentFilters
  });
});

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

Tehnika 5: Virtualizacija dugih lista

Renderiranje stotina ili tisuća DOM elemenata ubija presentation delay — tu nema ljepšeg načina da se to kaže. Virtualizacija rješava problem tako da renderira samo elemente vidljive u viewportu, drastično smanjujući DOM veličinu.

Ako imate listu od recimo 10.000 stavki, umjesto 10.000 DOM elemenata u pregledniku će postojati otprilike 8–15 (ovisno o veličini viewporta). Razlika u performansama je dramatična.

// React primjer s react-window (lagana virtualizacija)
import { FixedSizeList } from 'react-window';

function ProductList({ products }) {
  const Row = ({ index, style }) => (
    <div style={style} className="product-row">
      <img src={products[index].thumbnail} alt="" loading="lazy" />
      <span>{products[index].name}</span>
      <span>{products[index].price} EUR</span>
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      width="100%"
      itemCount={products.length}
      itemSize={80}
    >
      {Row}
    </FixedSizeList>
  );
  // 10.000 proizvoda, ali samo ~8 DOM elemenata u svakom trenutku!
}

Provjera i mjerenje napretka

Implementirali ste optimizacije — super. Ali kako znate da su stvarno pomogle?

Jedna bitna stvar: Google Search Console koristi podatke iz posljednjih 28 dana, pa ćete poboljšanja vidjeti tek nakon 2–4 tjedna. Nemojte paničariti ako se brojke odmah ne promijene. U međuvremenu, koristite ove alate za brzu validaciju:

  1. Chrome DevTools Performance panel — snimite sesiju, interagirajte sa stranicom i provjerite jesu li Long Tasks nestali ili se smanjili
  2. Web Vitals Extension — pratite INP u stvarnom vremenu dok klikćete po stranici
  3. web-vitals.js s atribucijskim modom — pošaljite INP podatke (uključujući LoAF) u svoj analitički sustav za praćenje poboljšanja na stvarnim korisnicima
// Praćenje INP s atribucijskim podacima
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  console.log('INP:', metric.value, 'ms');
  console.log('Element:', metric.attribution.interactionTarget);
  console.log('Input Delay:', metric.attribution.inputDelay, 'ms');
  console.log('Processing Time:', metric.attribution.processingDuration, 'ms');
  console.log('Presentation Delay:', metric.attribution.presentationDelay, 'ms');

  // LoAF podaci (Chrome 123+)
  if (metric.attribution.longAnimationFrameEntries?.length) {
    for (const loaf of metric.attribution.longAnimationFrameEntries) {
      console.log('LoAF trajanje:', loaf.duration, 'ms');
      for (const script of loaf.scripts) {
        console.log('  Skripta:', script.sourceURL, script.sourceFunctionName);
      }
    }
  }

  // Pošalji u analitiku
  sendToAnalytics({
    name: 'INP',
    value: metric.value,
    attribution: metric.attribution
  });
});

Kontrolna lista za INP optimizaciju

Za kraj, evo prioritetnog redoslijeda akcija. Počnite od vrha — često prva dva koraka riješe 80% problema:

  1. Revidirajte skripte trećih strana — najveći učinak, često najmanji napor. Uklonite nepotrebne, odgodite ostale.
  2. Lazy loadajte ne-kritične komponente — chat widgete, feedback alate, teške embede. Sve što korisnik ne treba u prvoj sekundi.
  3. Optimizirajte event handlere — profilirajte najsporije interakcije i razdvojite primarni od sekundarnog posla.
  4. Smanjite JavaScript payload — code splitting, tree shaking, moderni bundlevi. Manje JavaScripta = manje posla za glavnu nit.
  5. Poboljšajte renderiranje — virtualizacija lista, CSS containment, smanjenje DOM dubine.

Većina web stranica može postići prolazne INP rezultate rješavanjem samo prva dva koraka. U dublje optimizacije ulazite tek kad quick wins ne daju dovoljne rezultate.

Često postavljana pitanja (FAQ)

Što je dobar INP rezultat i zašto je prag 200 ms?

Google definira dobar INP kao 200 ms ili manje, mjereno na 75. percentilu korisnika. Prag od 200 ms odabran je na temelju istraživanja percepcije — iznad tog vremena korisnici počinju primjećivati kašnjenje. Zanimljiv podatak: stranice s INP-om ispod 200 ms imaju 24% nižu stopu napuštanja u usporedbi sa stranicama koje ne prolaze Core Web Vitals pragove.

Kako INP utječe na SEO i Google rangiranje?

INP je dio Core Web Vitals sustava i izravno utječe na Googleovo rangiranje. Od ožujka 2024. godine, kada je zamijenio FID, Google koristi INP kao službenu metriku odzivnosti. Stranice s lošim INP-om (iznad 500 ms) mogu doživjeti pad u rezultatima pretraživanja, posebno na mobilnim uređajima.

Zašto je INP na mobilnom gori nego na desktopu?

Kratki odgovor: slabiji hardver. Mobilni uređaji imaju sporije procesore, manje RAM-a i često koriste sporije mrežne veze. JavaScript koji se izvršava za 30 ms na desktopu može trajati 150 ms na mobilnom uređaju srednje klase — to je 5 puta sporije. A kad 68% web prometa dolazi s mobitela, optimizacija za mobilne performanse mora biti prioritet.

Mogu li popraviti INP bez promjene koda — samo konfiguracijskim promjenama?

Djelomično da. Uklanjanje ili odgoda skripti trećih strana (chat widgeta, nepotrebnih analitičkih alata, automatskih slidera) često donosi značajno poboljšanje bez ikakvog programiranja. Omogućavanje CDN-a za brže isporučivanje JavaScripta i korištenje HTTP/3 protokola također pomaže. Ali za dublje probleme — poput teških event handlera ili prevelikog DOM-a — promjene u kodu su neophodne.

Koji alati najbolje pomažu u dijagnostici INP problema?

Za identifikaciju problema koristite Google Search Console (pregledava INP po grupama URL-ova) i PageSpeed Insights (podaci na razini pojedinačne stranice). Za dijagnostiku su tu Chrome DevTools Performance panel za snimanje i analizu interakcija, Long Animation Frames (LoAF) API za preciznu atribuciju skripti, i web-vitals.js biblioteka s atribucijskim modom za praćenje INP-a na stvarnim korisnicima.

O Autoru Editorial Team

Our team of expert writers and editors.