Waarom INP de Metric Is Die Je Niet Kunt Negeren
Interaction to Next Paint — kortweg INP — is in 2026 de meest gefaalde Core Web Vital. Ruim 43% van alle websites haalt de drempelwaarde van 200 milliseconden niet. Laat dat even bezinken: bijna de helft van het web voelt traag aan zodra iemand klikt, tikt of typt. En ja, Google weegt dat keihard mee in de zoekresultaten.
INP verving in maart 2024 definitief First Input Delay (FID) als officiële Core Web Vital. En eerlijk gezegd? Dat werd tijd. Waar FID alleen de vertraging van de allereerste interactie mat, meet INP de responsiviteit van alle interacties gedurende het volledige paginabezoek. Het rapporteert de slechtste interactie (technisch gezien het 98e percentiel bij pagina's met veel interacties) als één enkele waarde. Dat maakt het een veel eerlijker en strenger meetinstrument dan FID ooit was.
In dit artikel nemen we alles door wat je nodig hebt om INP te begrijpen, te meten en — het belangrijkste — te verbeteren. Met werkende codevoorbeelden, moderne browser-API's zoals scheduler.yield() en de Long Animation Frames API, en concrete tips voor de populairste frameworks.
De Drie Fasen van Elke Interactie
Voordat je aan de slag gaat met optimaliseren, moet je snappen wat er precies gebeurt tussen het moment dat een gebruiker op een knop klikt en het moment dat de browser een visuele update toont. Elke interactie doorloopt drie meetbare fasen, en elk van die fasen kan je INP-score verpesten.
1. Input Delay — De Wachttijd
De input delay is de tijd tussen het moment waarop de gebruiker iets doet (een klik, een toetsaanslag) en het moment waarop de browser daadwerkelijk begint met het uitvoeren van de bijbehorende event handlers. Deze vertraging ontstaat wanneer de main thread bezet is met ander werk — denk aan het parsen van een groot JavaScript-bestand, het uitvoeren van een timer-callback, of het verwerken van een eerder gestarte taak.
Hier wordt het interessant. Data van Chrome toont een opvallend patroon: interacties tijdens het laden van de pagina hebben een mediaan input delay van 132ms, vergeleken met slechts 50ms na het laden. Dat is een factor 2,6 verschil. De opstartfase van je pagina is dus veruit de meest kwetsbare periode voor slechte INP-scores.
2. Processing Time — De Verwerking
Processing time is de totale tijd die nodig is om alle event handlers uit te voeren die aan de interactie zijn gekoppeld. Als een klikgebeurtenis meerdere event listeners triggert (denk aan een click-handler, een analytics-event én een formuliervalidatie), dan telt de gecombineerde uitvoeringstijd van al deze handlers mee.
En let op: het gaat hier niet alleen om jouw eigen code. Third-party scripts die luisteren naar dezelfde events dragen ook gewoon bij aan de processing time. Die chat-widget die op elke klik reageert? Telt mee. Die analytics-tracker? Telt ook mee.
3. Presentation Delay — Het Schilderen
De presentation delay is de tijd tussen het afronden van alle event handlers en het moment waarop de browser daadwerkelijk het volgende frame op het scherm schildert. Dit omvat style-herberekeningen, layout, compositing en het painten van pixels. Een grote of complexe DOM vertraagt deze fase behoorlijk, net als geforceerde reflows die door JavaScript worden veroorzaakt.
INP Meten: Van Velddata tot DevTools
Je kunt INP niet optimaliseren als je het niet kunt meten. Klinkt voor de hand liggend, maar je zou versteld staan hoeveel teams direct beginnen met "optimaliseren" zonder eerst te weten waar het pijnpunt zit. Er zijn twee fundamenteel verschillende manieren om INP-data te verzamelen, en je hebt ze allebei nodig.
Velddata (Real User Monitoring)
Velddata komt van echte gebruikers die je website bezoeken — dit is wat Google daadwerkelijk gebruikt voor de rankingbeoordeling. De belangrijkste bronnen:
- Google Search Console — toont INP-problemen per pagina op basis van het Chrome User Experience Report (CrUX), gemeten over een rollende periode van 28 dagen.
- PageSpeed Insights — geeft zowel veld- als labdata voor individuele URL's en toont de werkelijke INP-waarde van je gebruikers.
- web-vitals JavaScript-bibliotheek — meet INP direct in de browser en stuurt de data naar je eigen analytics-systeem. Dit is persoonlijk mijn favoriete optie omdat je volledige controle hebt over de data.
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
console.log('INP:', metric.value, 'ms');
console.log('Element:', metric.attribution.interactionTarget);
console.log('Type:', metric.attribution.interactionType);
// Stuur naar je analytics
fetch('/api/vitals', {
method: 'POST',
body: JSON.stringify({
name: 'INP',
value: metric.value,
target: metric.attribution.interactionTarget,
type: metric.attribution.interactionType
}),
keepalive: true
});
}, { reportAllChanges: true });
Labdata (DevTools en Lighthouse)
Labdata meet je in een gecontroleerde omgeving. Het nadeel: lab-testen simuleren niet de diversiteit aan apparaten en netwerkcondities van echte gebruikers. Het voordeel: je kunt problemen reproduceren en stap voor stap debuggen. Beide perspectieven zijn waardevol.
Open Chrome DevTools, ga naar het Performance-tabblad en neem een opname op terwijl je interacties uitvoert. Zoek naar lange taken (die rode balken bovenin) die samenvallen met je klik- of toetsgebeurtenissen. Sinds Chrome 123 zie je ook automatisch een Long Animation Frames-track in het flamechart, wat het debuggen een stuk makkelijker maakt.
De Long Animation Frames API: Jouw Beste Debugging-Wapen
Oké, nu wordt het echt interessant. De Long Animation Frames API (LoAF — uitgesproken als "loaf", ja echt) is de opvolger van de Long Tasks API en het krachtigste gereedschap dat je hebt om INP-problemen te diagnosticeren. Het verschil is simpel: waar INP je vertelt dat er een probleem is, vertelt LoAF je waarom.
Een animatieframe wordt als "lang" beschouwd wanneer het langer duurt dan 50 milliseconden. LoAF geeft je voor elk lang frame gedetailleerde informatie: welke scripts werden uitgevoerd, hoe lang elk script duurde, welke fase (style, layout, paint) de meeste tijd kostte, en — cruciaal — de blockingDuration, oftewel de tijd dat de main thread daadwerkelijk geblokkeerd was voor gebruikersinput.
LoAF Observeren in Code
// Controleer of de browser LoAF ondersteunt
if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Filter op frames met daadwerkelijke blocking duration
if (entry.blockingDuration > 0) {
console.group(
`LoAF: ${Math.round(entry.duration)}ms ` +
`(blocking: ${Math.round(entry.blockingDuration)}ms)`
);
// Bekijk welke scripts verantwoordelijk zijn
for (const script of entry.scripts) {
console.log(
`Script: ${script.sourceURL || 'onbekend'}`,
`\n Functie: ${script.sourceFunctionName || 'anoniem'}`,
`\n Duur: ${Math.round(script.duration)}ms`,
`\n Type: ${script.invokerType}`
);
}
console.groupEnd();
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
}
De scripts-array is het goud van LoAF. Elk PerformanceScriptTiming-entry (voor scripts die langer dan 5ms duurden) bevat de bron-URL, de functienaam, het aanroeptype (event-listener, promise-resolver, timer-callback) en de exacte duur. Hiermee kun je direct zien welk third-party script of welke event handler de boosdoener is.
LoAF Koppelen aan INP met web-vitals v4
Versie 4 van de web-vitals bibliotheek koppelt automatisch de relevante Long Animation Frames aan de INP-interactie. Dat is enorm handig, want het betekent dat je voor elke slechte INP-meting direct de bijbehorende scripts en hun duur kunt opvragen:
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
const loafs = metric.attribution.longAnimationFrameEntries;
if (loafs && loafs.length > 0) {
const langsteFrame = loafs[0];
console.log('INP-waarde:', metric.value, 'ms');
console.log('Blocking duration:', langsteFrame.blockingDuration, 'ms');
console.log('Verantwoordelijke scripts:');
for (const script of langsteFrame.scripts) {
console.log(
` - ${script.sourceURL} (${script.sourceFunctionName}): ` +
`${Math.round(script.duration)}ms`
);
}
}
}, { reportAllChanges: true });
scheduler.yield(): De Main Thread Vrijgeven Zonder Controle te Verliezen
Een van de krachtigste nieuwe browser-API's voor INP-optimalisatie is scheduler.yield(). Het probleem met lange JavaScript-taken is eigenlijk heel simpel: de browser kan ze niet onderbreken. Als een functie 300 milliseconden duurt, is de main thread 300 milliseconden volledig geblokkeerd voor gebruikersinput. Geen klikken, geen typen, niks.
De traditionele oplossing — setTimeout(fn, 0) — plaatst je vervolgwerk achteraan de taakwachtrij. Dat klinkt prima, maar in de praktijk betekent het dat andere wachtende taken (timers, third-party callbacks) eerst worden uitgevoerd voordat jouw code weer aan bod komt. Dat leidt tot onvoorspelbare vertragingen, en dat is precies wat je probeert te vermijden.
scheduler.yield() lost dit slim op: het geeft de main thread vrij zodat de browser eventuele gebruikersinteracties kan verwerken, maar plaatst jouw vervolgwerk vooraan de wachtrij in plaats van achteraan. Best of both worlds, zeg maar.
Praktisch Voorbeeld: Een Lange Lijst Verwerken
// SLECHT: blokkeert de main thread volledig
function verwerkAlleItems(items) {
for (const item of items) {
doeZwareBerekening(item);
}
updateDOM();
}
// GOED: geeft periodiek controle terug
async function verwerkAlleItems(items) {
const BATCH_GROOTTE = 50;
for (let i = 0; i < items.length; i += BATCH_GROOTTE) {
const batch = items.slice(i, i + BATCH_GROOTTE);
for (const item of batch) {
doeZwareBerekening(item);
}
// Geef de main thread vrij voor gebruikersinteracties
if (globalThis.scheduler?.yield) {
await scheduler.yield();
} else {
// Fallback voor browsers zonder scheduler.yield()
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
updateDOM();
}
Event Handlers Opsplitsen
Een patroon dat ik keer op keer tegenkom bij INP-problemen: één enkele click-handler die véél te veel werk doet. De oplossing? Toon visuele feedback direct en stel het zware werk uit:
button.addEventListener('click', async () => {
// Stap 1: toon direct visuele feedback (kritiek voor INP)
button.textContent = 'Laden...';
button.disabled = true;
// Geef de browser de kans om de visuele update te schilderen
if (globalThis.scheduler?.yield) {
await scheduler.yield();
} else {
await new Promise((r) => setTimeout(r, 0));
}
// Stap 2: voer het zware werk uit NA de visuele update
const resultaat = await berekenResultaat();
toonResultaat(resultaat);
button.disabled = false;
});
scheduler.yield() wordt momenteel ondersteund in Chrome 115+ en Edge 115+. Firefox werkt eraan, en Safari heeft nog geen publiek tijdschema (helaas). Gebruik altijd het fallback-patroon met feature detection zodat je code in alle browsers werkt.
Framework-Specifieke INP-Optimalisaties
Moderne JavaScript-frameworks introduceren hun eigen patronen die INP behoorlijk kunnen beïnvloeden — zowel positief als negatief. Laten we de meest impactvolle optimalisaties per framework doorlopen.
React: Concurrent Features Benutten
React 18+ biedt ingebouwde mechanismen om de main thread vrij te houden tijdens state-updates. De twee krachtigste tools zijn useTransition en useDeferredValue. Als je deze nog niet gebruikt, laat je gratis INP-winst op tafel liggen.
import { useState, useTransition, useDeferredValue } from 'react';
function ZoekComponent() {
const [zoekterm, setZoekterm] = useState('');
const [isPending, startTransition] = useTransition();
const uitgesteldeZoekterm = useDeferredValue(zoekterm);
function handleInput(e) {
// De input-update is urgent — direct verwerken
setZoekterm(e.target.value);
// De zoekresultaten zijn niet-urgent — uitstellen
startTransition(() => {
// React geeft de main thread vrij tussen re-renders
// zodat de gebruiker kan blijven typen
});
}
return (
<div>
<input
type="text"
value={zoekterm}
onChange={handleInput}
placeholder="Zoeken..."
/>
{isPending && <span>Laden...</span>}
<ZoekResultaten zoekterm={uitgesteldeZoekterm} />
</div>
);
}
Daarnaast voorkomt React.memo onnodige re-renders van child-componenten. En met React 19's React Compiler wordt memoization automatisch afgehandeld — waardoor handmatige useMemo- en useCallback-optimalisaties grotendeels overbodig worden. Dat scheelt een hoop boilerplate.
Vue: Async Components en v-memo
Vue biedt defineAsyncComponent voor lazy loading van zware componenten en de v-memo-directive om onnodige re-renders te voorkomen:
import { defineAsyncComponent } from 'vue';
// Laad de zware grafiek-component alleen wanneer nodig
const ZwareGrafiek = defineAsyncComponent(() =>
import('./ZwareGrafiek.vue')
);
// In je template: v-memo voorkomt re-renders
// tenzij de opgegeven dependencies veranderen
// <div v-memo="[datasetId]">
// <ZwareGrafiek :data="grafiekData" />
// </div>
Angular: OnPush en @defer
Angular biedt de OnPush change detection strategy om onnodige change detection-cycli te vermijden, en het nieuwere @defer-blok voor het uitstellen van niet-kritieke template-secties:
// Component met OnPush strategie
@Component({
selector: 'app-product-lijst',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ul>
@for (product of producten; track product.id) {
<li>{{ product.naam }}</li>
}
</ul>
@defer (on viewport) {
<app-product-reviews [productId]="geselecteerdProductId" />
} @placeholder {
<p>Scroll naar beneden voor reviews...</p>
}
`
})
export class ProductLijstComponent {
producten = input.required<Product[]>();
}
Third-Party Scripts: De Stille Vernietigers van INP
Dit is eerlijk gezegd het punt waar ik de meeste winst zie bij websites die worstelen met INP. Third-party scripts — chat-widgets, analytics-bibliotheken, advertentie-scripts, social-media-embeds — voegen allemaal JavaScript toe dat op de main thread draait en concurreert met je eigen event handlers.
Audit en Prioriteer
Gebruik de LoAF API om te identificeren welke third-party scripts de meeste blocking duration veroorzaken. Stel jezelf bij elk script de vraag: is de waarde die dit script levert groter dan de INP-impact die het veroorzaakt? Je zult versteld staan hoe vaak het antwoord "nee" is.
Concrete maatregelen die je kunt nemen:
- Uitstellen — laad niet-essentiële scripts met
asyncofdefer, of nog beter: laad ze pas na gebruikersinteractie via een facade-patroon. - Web Workers — verplaats zware berekeningen naar een Web Worker zodat ze de main thread niet blokkeren.
- Facade-patroon — toon een lichtgewicht placeholder (een afbeelding of CSS-animatie) voor zware embeds zoals YouTube-video's of chat-widgets, en laad de echte embed pas wanneer de gebruiker erop klikt. Simpel maar ongelooflijk effectief.
- Verwijderen — wees niet bang om scripts te verwijderen die weinig waarde toevoegen. Elke verwijderde kilobyte JavaScript is een directe verbetering voor INP.
// Facade-patroon voor een chat-widget
const chatKnop = document.querySelector('#chat-facade');
chatKnop.addEventListener('click', async () => {
chatKnop.textContent = 'Chat laden...';
// Laad de echte chat-widget pas bij de eerste klik
const { initChat } = await import('./chat-widget.js');
initChat();
}, { once: true });
DOM-Grootte en Rendering Optimaliseren
Een te grote DOM is een veelonderschatte INP-killer. Serieus. Elke interactie die een visuele update triggert, dwingt de browser om style-herberekeningen en layout-berekeningen uit te voeren over (een deel van) de DOM-boom. Hoe groter de boom, hoe langer dat duurt — en dat telt allemaal mee in de presentation delay van je INP.
Google raadt aan om te streven naar:
- Maximaal 1.400 DOM-elementen totaal
- Een maximale diepte van 32 niveaus
- Maximaal 60 kinderen per parent-element
Klinkt misschien streng, maar de meeste websites zitten hier ver boven zonder het te beseffen.
Geforceerde Reflows Vermijden
Een van de duurste operaties die je kunt veroorzaken is een geforceerde reflow (ook wel "layout thrashing" genoemd). Dit gebeurt wanneer je in JavaScript eerst een DOM-element wijzigt en vervolgens een layout-eigenschap uitleest:
// SLECHT: veroorzaakt geforceerde reflow bij elke iteratie
for (const item of items) {
item.style.width = '100px';
const hoogte = item.offsetHeight; // forceert reflow!
item.style.height = `${hoogte * 2}px`;
}
// GOED: lees eerst alles, schrijf dan alles
const hoogtes = items.map((item) => item.offsetHeight);
items.forEach((item, i) => {
item.style.width = '100px';
item.style.height = `${hoogtes[i] * 2}px`;
});
CSS contain voor Render-isolatie
De CSS-eigenschap contain vertelt de browser dat een element en zijn inhoud onafhankelijk zijn van de rest van de pagina. Dit stelt de browser in staat om rendering-berekeningen te isoleren, waardoor wijzigingen binnen dat element niet de hele pagina beïnvloeden:
/* Isoleer onafhankelijke secties van de pagina */
.sidebar,
.comments-sectie,
.gerelateerde-artikelen {
contain: content;
}
/* Strengere isolatie voor elementen met vaste afmetingen */
.advertentie-blok {
contain: strict;
width: 300px;
height: 250px;
}
Praktisch Stappenplan: Van Slecht naar Goed INP
Goed, genoeg theorie. Hier is een concreet stappenplan dat je kunt volgen om de INP van je website systematisch te verbeteren:
- Meet je huidige INP — controleer Google Search Console voor velddata en gebruik PageSpeed Insights voor individuele pagina's. Noteer de huidige p75-waarde.
- Identificeer de slechtste interacties — gebruik de web-vitals bibliotheek met attribution build om te loggen welke interacties en elementen de hoogste INP-waarden veroorzaken.
- Analyseer met LoAF — koppel Long Animation Frames data aan je INP-metingen om te achterhalen welke scripts en fasen (input delay, processing, presentation) verantwoordelijk zijn.
- Pak de opstartfase aan — verlaag de input delay door JavaScript te splitsen, niet-kritieke scripts uit te stellen en
scheduler.yield()te gebruiken in lange taken. - Optimaliseer event handlers — splits zware handlers op, toon direct visuele feedback en verplaats niet-kritiek werk naar na de visuele update.
- Audit third-party scripts — meet de impact van elk third-party script op blocking duration en verwijder, vervang of stel scripts uit die meer kosten dan ze opleveren.
- Verklein de DOM — gebruik content-visibility, virtualisatie voor lange lijsten en CSS contain om rendering-kosten te beperken.
- Monitor continu — stel alerts in voor INP-regressies en integreer Lighthouse CI in je deployment-pipeline. Want INP is geen eenmalige fix — het is iets dat je voortdurend in de gaten moet houden.
Veelgestelde Vragen over INP
Wat is een goede INP-score in 2026?
Een INP van 200 milliseconden of lager wordt door Google als "goed" beschouwd. Tussen 200ms en 500ms heeft je pagina verbetering nodig, en boven 500ms is de responsiviteit ronduit slecht. Google meet het 75e percentiel van je gebruikers over een periode van 28 dagen — dat betekent dat 75% van je bezoekers een INP onder de drempelwaarde moet ervaren om groen te scoren.
Wat is het verschil tussen INP en FID?
FID (First Input Delay) mat alleen de vertraging van de allereerste interactie op een pagina en negeerde de verwerkingstijd en presentatievertraging volledig. INP meet daarentegen alle interacties gedurende het hele paginabezoek en neemt de volledige duur mee — van input tot visuele update. Daardoor is INP een veel completere metric, maar ook een stuk lastiger om te halen.
Heeft INP invloed op mijn Google-ranking?
Ja, absoluut. INP is sinds maart 2024 een officiële Core Web Vital en onderdeel van Google's page experience-signalen. Hoewel content nog steeds verreweg de belangrijkste rankingfactor is, kan een slechte INP-score je ranking negatief beïnvloeden — vooral wanneer concurrerende pagina's vergelijkbare content bieden maar een betere gebruikerservaring leveren.
Hoe meet ik INP op mijn eigen website?
De snelste manier is via Google Search Console (sectie "Core Web Vitals") voor velddata van echte gebruikers. Voor individuele pagina's gebruik je PageSpeed Insights. Wil je real-time debuggen in de browser? Open Chrome DevTools en neem een Performance-opname op terwijl je interacties uitvoert. En voor continue monitoring integreer je de web-vitals JavaScript-bibliotheek in je eigen analytics.
Kan ik INP verbeteren zonder mijn JavaScript te herschrijven?
Gedeeltelijk. Er zijn zeker quick wins die geen herschrijving vereisen: het verwijderen of uitstellen van ongebruikte third-party scripts, het verkleinen van de DOM, het toevoegen van CSS contain, en het facade-patroon voor zware embeds. Maar voor structureel goede INP-scores ontkom je er meestal niet aan om event handlers te optimaliseren en lange JavaScript-taken op te splitsen — en ja, dat vereist codewijzigingen.