dataLayer events inplannen om de INP te optimaliseren
Hoe GTM-events uitstellen tot na de browser paint je INP-scores verbetert
TL;DR: INP verbeteren door dataLayer.push() uit te stellen
Wanneer je dataLayer.push() synchroon afvuurt in een event handler, verwerkt GTM tags voordat de browser visuele feedback kan tonen. Dit blokkeert de main thread en verhoogt je Interaction to Next Paint (INP). De oplossing: plan dataLayer.push() in om te draaien nadat de browser het volgende frame heeft getekend via requestAnimationFrame. Dit patroon verlaagt de INP op de meeste sites met 20 tot 100 ms. Het verandert vaak een onvoldoende score in een voldoende. De dataverzameling loopt 50 tot 250 ms vertraging op. Dit is onbeduidend voor analytics en marketing tracking.
Hoe dataLayer.push() je INP schaadt
Voor een van onze klanten maten we een daling van 100 ms in Interaction to Next Paint (INP) door dataLayer.push() opnieuw in te plannen na een gebruikersinteractie. De oplossing is een JavaScript drop-in vervanging die rendering prioriteit geeft boven tracking.
Laatst beoordeeld door Arjen Karel in maart 2026
Table of Contents!
- TL;DR: INP verbeteren door dataLayer.push() uit te stellen
- Hoe dataLayer.push() je INP schaadt
- Het probleem met synchrone dataLayer.push()
- De oplossing: eerst tekenen, dan pushen
- De awaitPaint helper
- Globale override voor snel testen
- Waarom dit de INP verbetert
- Waarom geen vaste vertraging, Idle Callback of scheduler.yield()?
- Afwegingen
Het probleem met synchrone dataLayer.push()
Als je met Google Tag Manager werkt, ken je dataLayer.push(). Het is de standaardmethode om events naar de Data Layer te sturen. Het is diep geïntegreerd in de meeste site-functionaliteiten. Het probleem is dat niemand zich afvraagt wannéér het uitvoert.
Als je dataLayer.push() direct aanroept in een event handler (een knopklik, formulierverzending, menu-toggle), draait het synchroon. GTM-tags die op dat event vuren, voeren ook direct uit. Ze blokkeren de main thread voordat de browser de lay-out kan updaten. De bezoeker klikt op een knop en ziet niets gebeuren terwijl GTM tracking scripts op de achtergrond verwerkt. Die vertraging is je presentation delay. Het is de grootste oorzaak van een slechte INP.
De onderstaande performance trace van een grote nieuwswebsite toont GTM-activiteit na een gebruikersinteractie. De GTM-taken duurden ongeveer 90 ms. Dit duwde de totale INP naar 263 ms. Die interactie faalt voor de Core Web Vitals.

De oplossing: eerst tekenen, dan pushen
De oplossing sluit aan bij de INP-optimalisatiegids van web.dev: yield naar de main thread zodat de browser kan tekenen voordat niet-kritieke code draait. In plaats van tracking synchroon uit te voeren in de event handler:
- Voer de code voor de visuele update direct uit.
- Laat de browser tekenen.
- Push daarna naar de dataLayer.
Hier is dezelfde interactie na het toepassen van dit patroon. De enige verandering is dat dataLayer.push() nu draait nadat de browser het volgende frame heeft getekend.

De interactie die eerder faalde, slaagt nu ruim. Alle data bereikt de dataLayer nog steeds. Het verschil is dat GTM-scripts uitvoeren nadat de browser reageert op de actie van de gebruiker, niet ervoor.
De awaitPaint helper
Deze helper gebruikt requestAnimationFrame om een callback in te plannen nadat de browser heeft getekend. De geneste setTimeout zorgt ervoor dat de functie draait nadat de paint klaar is, niet vlak ervoor. Dit is het aanbevolen patroon van web.dev om niet-kritiek werk uit te stellen na een interactie.
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: voorkomt vastlopen als RAF nooit afvuurt (bijv. achtergrondtabbladen)
setTimeout(resolve, 200);
// Inplannen na de volgende paint
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
De helper gebruiken
Wikkel elke dataLayer.push() aanroep in awaitPaint om hem uit te stellen tot na de paint:
function pushToDataLayer(event, data) {
window.dataLayer = window.dataLayer || [];
awaitPaint(() => {
window.dataLayer.push({
event,
...data,
timestamp: new Date().toISOString()
});
});
}
// Gebruik
document.querySelector('.buy-button').addEventListener('click', () => {
// Visuele feedback gebeurt direct
showLoadingSpinner();
// Tracking vuurt af na paint
pushToDataLayer('purchase_click', { productId: '123' });
});
Globale override voor snel testen
Om dit patroon over je hele site te testen zonder elke dataLayer.push() aanroep te herschrijven, kun je de push-functie globaal overschrijven. Plaats dit script in de <head> direct na de GTM-container snippet.
<script type="module">
window.dataLayer = window.dataLayer || [];
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: voorkomt vastlopen als RAF nooit afvuurt (bijv. achtergrondtabbladen)
setTimeout(resolve, 200);
// Inplannen na de volgende paint
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
if (window.dataLayer && typeof window.dataLayer.push === 'function') {
const originalPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function (...args) {
awaitPaint(() => {
originalPush(...args);
});
};
}
</script>
Dit overschrijft elke dataLayer.push() op de pagina. Alle bestaande GTM-event triggers, hardcoded tracking calls en plugin-integraties profiteren hier automatisch van zonder code-aanpassingen.
Waarom dit de INP verbetert
De INP meet de tijd vanaf een gebruikersinteractie totdat de browser de visuele reactie tekent. Als je de verwerking van GTM-events synchroon uitvoert in een event handler, blokkeer je de main thread en voorkom je rendering. De Web Almanac van 2024 ontdekte dat presentation delay de grootste oorzaak is van een slechte INP, en tracking scripts behoren tot de hoofdoorzaken.
De cijfers bevestigen dit. De Web Almanac van 2025 toont dat 77% van de mobiele pagina's de INP haalt, maar slechts 63% van de top 1.000 best bezochte sites. Die topsites laden meer third-party scripts. Bij Subito (de grootste online marktplaats van Italië) zorgde het uitschakelen van één TikTok tracking script geladen via GTM voor een daling van de INP van 208 ms naar ongeveer 170 ms. Eén script, 38 ms bespaard.
Door dataLayer.push() uit te stellen tot na de paint, haal je alle GTM-verwerking uit het kritieke pad van de interactie. De gebruiker krijgt direct visuele feedback. GTM vuurt nog steeds, alleen 50 tot 250 ms later.
Waarom geen vaste vertraging, Idle Callback of scheduler.yield()?
- Vaste
setTimeout(delay): Een hardcoded vertraging (bijv.setTimeout(..., 100)) is gokken wanneer de rendering klaar is. Te lang en je vertraagt tracking onnodig. Te kort en je blokkeert de paint nog steeds. requestIdleCallback: Plant werk in wanneer de browser idle is, maar garandeert geen vlotte uitvoering direct na een specifieke interactie. De callback draait mogelijk veel later, of helemaal niet voordat de gebruiker de pagina verlaat.scheduler.yield(): De moderne API voor yielding naar de main thread. Het behoudt taakprioriteit (in tegenstelling totsetTimeout, die je vervolgtaak achteraan in de wachtrij plaatst). Echter garandeertscheduler.yield()niet dat er een paint is geweest voordat je code draait. Voor deze specifieke use case isrequestAnimationFramede betere tool, omdat het gekoppeld is aan de rendering lifecycle. Let op datscheduler.yield()nog niet wordt ondersteund in Safari.
De combinatie requestAnimationFrame + setTimeout is specifiek ontworpen voor dit scenario. requestAnimationFrame vuurt net voordat de browser tekent. De geneste setTimeout creëert een nieuwe taak die draait nadat die paint klaar is. Samen garanderen ze dat je uitgestelde code uitvoert ná de visuele update. Dat is exact wat de INP meet.
Afwegingen
- Micro-vertraging in dataverzameling: Events bereiken GTM 50 tot 250 ms later dan bij synchroon pushen. Voor analytics en marketing tracking is dit onbeduidend.
- Dataverlies bij snel vertrek: Als een bezoeker een event triggert en de pagina verlaat binnen het vertragingsvenster, vuurt dat event mogelijk niet af. Gebruik voor kritieke events vóór een redirect of het verlaten van de pagina
navigator.sendBeacon()in plaats vandataLayer.push(). Zie de argumenten voor het beperken van analytics scripts voor meer over de Beacon API.
Voor de meeste tracking weegt de INP-winst ruimschoots op tegen de minimale vertraging. Als je milliseconde-precisie nodig hebt in event logging (real-time bidding, financiële dashboards), is deze aanpak niet geschikt. Voor al het andere is het een duidelijke winst.
Verifieer na het toepassen van de override de verbetering met Real User Monitoring. Lab tests laten het verschil zien, maar field data van echte gebruikers op echte netwerken is wat telt voor je Core Web Vitals scores.
Ik krijg sites door de Core Web Vitals.
500K+ pagina's voor grote Europese uitgevers en e-commerce. Ik schrijf de fixes en check ze in de echte data.
Zo werk ik