dataLayer Events schedulen om de INP te optimaliseren
GTM events uitstellen totdat de layout stabiliseert voor verbeterde INP-waardes

TL;DR: INP verbeteren door Google Tag Manager te optimaliseren
Het Probleem: Standaard Google Tag Manager (dataLayer.push())-aanroepen, vooral wanneer ze direct worden getriggerd door gebruikersinteracties (zoals klikken of tikken), kunnen het vermogen van de browser om visuele updates te tonen vertragen. Dit heeft een negatieve invloed op de Interaction to Next Paint (INP) score, omdat de browser gedwongen wordt GTM-taken te verwerken voordat de visuele feedback voor die interactie wordt gerenderd.
De Oplossing: We kunnen deze dataLayer.push()-aanroepen uitstellen tot nadat de browser het volgende frame heeft gerenderd. Dit geeft prioriteit aan het renderen van onmiddellijke visuele feedback voor de gebruiker. De oplossing bestaat uit een klein JavaScript-fragment dat het standaardgedrag van dataLayer.push() aanpast om dit uitstel te implementeren.
Het Voordeel: Deze aanpak leidt voor onze klanten doorgaans tot een vermindering van 20 ms tot 100 ms in INP, waardoor falende Core Web Vitals-scores vaak veranderen in voldoende scores. Gebruikers ervaren een merkbaar snellere interface. Hoewel de dataverzameling voor GTM enigszins wordt vertraagd (meestal 50-250 ms), is dit voor de meeste analyse- en marketingdoeleinden een acceptabele trade-off.
INP-uitdagingen door de uitvoering van Google Tag Manager aanpakken
Voor een van onze klanten zagen we een vermindering van 100 ms in hun Interaction to Next Paint (INP) metric door simpelweg het uitvoeringsmoment van de dataLayer.push()-functie na een gebruikersinteractie opnieuw te plannen. Deze verbetering werd bereikt met een eenvoudig toe te passen en te testen JavaScript "drop-in" vervanging die prioriteit geeft aan rendering.
Table of Contents!
- TL;DR: INP verbeteren door Google Tag Manager te optimaliseren
- Het INP-probleem met dataLayer.push()
- De oplossing: geef prioriteit aan de paint, push daarna naar de datalayer!
- De Code Toepassen
- Eenvoudig Testen: Globale Override
- Waarom dit de Interaction to Next Paint helpt
- Waarom niet gewoon een vaste vertraging, idle Callback of Scheduler gebruiken?
- Trade-offs
Het INP-probleem met dataLayer.push()
Als je met Google Tag Manager (GTM) hebt gewerkt, ben je bekend met dataLayer.push()
. Het is de standaardmethode om data of events naar de Data Layer te sturen, waardoor tags kunnen worden geactiveerd. Het wordt veel gebruikt, is diep geïntegreerd in veel sitefunctionaliteiten en de prestatie-implicaties ervan worden zelden in twijfel getrokken. Ervan uitgaan dat het altijd op het optimale moment voor de user experience wordt uitgevoerd, kan echter problematisch zijn.
Wanneer dataLayer.push()
direct wordt aangeroepen binnen een event handler voor een gebruikersinteractie (bijv. een klik op een knop), wordt het doorgaans synchroon uitgevoerd. Dit betekent dat alle GTM-tags die zijn geconfigureerd om op basis van die event te worden geactiveerd, ook onmiddellijk proberen uit te voeren en de main thread blokkeren vóór een layout-update. Deze blokkade voorkomt dat de browser snel de visuele veranderingen kan renderen die de gebruiker verwacht na de interactie (bijv. het openen van een menu, het tonen van een laadspinner), wat leidt tot een slechte INP-score.
Laten we onderzoeken wat er gebeurt. De onderstaande performance trace, van een grote nieuwswebsite, toont GTM-gerelateerde activiteit na een gebruikersinteractie. In dit geval duurde de uitvoering van de GTM-taken ongeveer 90 ms, en met een totale INP-waarde van 263 ms faalt deze interactie voor de Core Web Vitals!
De oplossing: geef prioriteit aan de paint, push daarna naar de datalayer!
De oplossing is zowel eenvoudig als elegant en sluit aan bij de best practices voor INP-optimalisatie: geef prioriteit aan de snelheidsperceptie van de gebruiker. In plaats van alle code (afhandeling van interacties, visuele updates en GTM-tracking) synchroon uit te voeren, moeten we:
- De kritieke code voor de visuele update onmiddellijk uitvoeren.
- De browser deze visuele veranderingen laten renderen.
- Daarna minder kritieke code uitvoeren, zoals het pushen van events naar de dataLayer.
Deze aanpak wordt vaak "yielding to the main thread" genoemd. Laten we de impact bekijken wanneer we dit yielding-patroon toepassen op dataLayer.push()
-aanroepen op dezelfde site en voor exact dezelfde interactie als voorheen. Het enige verschil is dat we de dataLayer.push()
hebben ingepland om plaats te vinden nadat de browser de kans heeft gehad om het volgende frame te renderen met requestAnimationFrame
.
Zoals je kunt zien, slaagt dezelfde interactie die voorheen faalde nu ruimschoots voor de Core Web Vitals. Alle benodigde data wordt nog steeds naar de dataLayer verzonden. Het cruciale verschil is dat GTM-gerelateerde scripts nu worden uitgevoerd nadat de browser de layout heeft bijgewerkt als reactie op de actie van de gebruiker. Dit betekent dat je bezoeker onmiddellijke visuele feedback krijgt, wat de user experience verbetert, in plaats van te moeten wachten tot tracking-scripts zijn verwerkt.
De Code Toepassen
De code werkt door de standaard dataLayer.push()-functie te overschrijven met een aangepaste functie die de data naar de dataLayer pusht nadat een layout-update is uitgevoerd.
Await Paint-hulpfunctie
Deze hulpfunctie gebruikt requestAnimationFrame
om een callback in te plannen die wordt uitgevoerd nadat de browser het volgende frame heeft gerenderd.
// --- Implementatie van het INP Yield Pattern --- // Deze hulpfunctie zorgt ervoor dat een functie pas wordt uitgevoerd na de volgende paint (of een veilige fallback) async function awaitPaint(fn) { await new Promise((resolve) => { // Fallback timeout: zorgt ervoor dat we niet oneindig wachten als RAF nooit wordt geactiveerd setTimeout(resolve, 200); // Vraag het volgende animation frame aan (signaal dat de browser klaar is om te renderen) requestAnimationFrame(() => { // Kleine vertraging om te verzekeren dat het frame daadwerkelijk is gerenderd, niet alleen in de wachtrij staat setTimeout(resolve, 50); }); }); // Zodra de paint (of fallback) plaatsvindt, voer de opgegeven functie uit if (typeof fn === 'function') { fn(); } }
Implementatievoorbeeld
Dit is een voorbeeld van een React-hulpfunctie die de dataLayer-push automatisch inplant.
export const pushToDataLayer = (event: string, data: Record<string, any> = {}): void => { // Verzeker dat dataLayer bestaat if (typeof window !== 'undefined') { window.dataLayer = window.dataLayer || []; // wacht op paint awaitPaint(() => { // Push event en data naar dataLayer window.dataLayer.push({ event, ...data, timestamp: new Date().toISOString() }); }); } }; // Gebruik in een React component: // import { useState, useEffect } from 'react'; // import { pushToDataLayer } from '../utils/analytics'; // function ProductCard({ product }) { // const [isWishlisted, setIsWishlisted] = useState(false); // // Volg wijzigingen in de wishlist // useEffect(() => { // if (isWishlisted) { // pushToDataLayer('addToWishlist', { // productId: product.id, // productName: product.name, // productPrice: product.price // }); // } // }, [isWishlisted, product]); // return ( // <div className="product-card"> // <h3>{product.name}</h3> // <p>${product.price}</p> // <button onClick={() => setIsWishlisted(!isWishlisted)}> // {isWishlisted ? '♥' : '♡'} {isWishlisted ? 'Wishlisted' : 'Add to Wishlist'} // </button> // </div> // ); // }
Eenvoudig Testen: Globale Override
Om dit patroon snel te testen op je hele site of voor bestaande dataLayer.push()-implementaties zonder elke implementatie te moeten refactoren, kun je de dataLayer.push()-functie globaal overschrijven.
Belangrijk: Plaats dit script hoog in de <head> van je HTML, direct nadat het GTM-container-script is geladen. Dit zorgt ervoor dat je override zo snel mogelijk actief is.
<script type="module"> // Verzeker dat dataLayer bestaat (standaardonderdeel van het GTM-fragment) window.dataLayer = window.dataLayer || []; // --- INP Yield Pattern Hulpfunctie --- async function awaitPaint(fn) { return new Promise((resolve) => { const fallbackTimeout = setTimeout(() => { if (typeof fn === 'function') { fn(); } resolve(); }, 200); requestAnimationFrame(() => { setTimeout(() => { clearTimeout(fallbackTimeout); if (typeof fn === 'function') { fn(); } resolve(); }, 50); }); }); } // --- Het patroon globaal toepassen op Google Tag Manager dataLayer.push --- if (window.dataLayer && typeof window.dataLayer.push === 'function') { // Bewaar de originele push-functie const originalDataLayerPush = window.dataLayer.push.bind(window.dataLayer); // Overschrijf dataLayer.push window.dataLayer.push = function (...args) { // Gebruik een IIFE om async/await-syntaxis te kunnen gebruiken, // of roep awaitPaint direct aan. (async () => { await awaitPaint(() => { // Roep de originele push aan met zijn argumenten na het yielden voor de paint originalDataLayerPush(...args); }); })(); // Geef de waarde terug die de originele push zou hebben, indien van toepassing (hoewel meestal undefined) // Voor GTM heeft de push-methode geen betekenisvolle returnwaarde voor de aanroeper. // Het primaire doel is het neveneffect van het toevoegen aan de wachtrij. }; console.log('dataLayer.push is overschreven om de INP te verbeteren.'); } </script>
Waarom dit de Interaction to Next Paint helpt
INP meet de latency vanaf een gebruikersinteractie (bijv. klik, tik, toetsaanslag) totdat de browser de volgende visuele update als reactie op die interactie rendert. Als je direct na een interactie resource-intensieve taken, zoals de verwerking van GTM-events en het activeren van tags, synchroon uitvoert, blokkeer je de main thread van de browser. Dit voorkomt het renderen van de visuele feedback die de gebruiker verwacht. Door niet-kritieke JavaScript-uitvoering zoals GTM-tracking uit te stellen tot nadat de browser de visuele updates heeft gerenderd, zorgt dit patroon ervoor dat gebruikers snelle visuele feedback krijgen, wat de INP-score aanzienlijk verbetert.
Waarom niet gewoon een vaste vertraging, idle Callback of Scheduler gebruiken?
- Vaste
setTimeout(delay)
: Het gebruik van een hardgecodeerde vertraging (bijv. setTimeout(..., 100)) is in wezen gokken wanneer de rendering voltooid zal zijn. Het is niet adaptief; de vertraging kan te lang zijn (waardoor tracking onnodig wordt vertraagd) of te kort (waardoor de paint alsnog wordt geblokkeerd). requestIdleCallback
: Deze API plant werk in wanneer de browser idle is. Hoewel dit nuttig is voor achtergrondtaken, garandeert het geen snelle uitvoering direct na de visuele update van een specifieke interactie. De callback kan veel later worden uitgevoerd of, tijdens drukke periodes, helemaal niet voordat de gebruiker de pagina verlaat.- Generieke Schedulers (
postTask
etc.): Hoewel depostTask
scheduler van de browser prioritering biedt, isrequestAnimationFrame
specifiek gekoppeld aan de rendering-levenscyclus. DeawaitPaint
hulpfunctie maakt hier gebruik van doorrequestAnimationFrame
te gebruiken als een signaal dat de browser zich voorbereidt om te renderen, en voegt dan een minimale vertraging toe om te activeren nadat die paint waarschijnlijk is voltooid.
Trade-offs
Elke optimalisatie heeft potentiële trade-offs.
- Microvertraging in dataverzameling: Deze techniek introduceert een voorspelbare microvertraging (ongeveer 50-250 ms, afhankelijk van de browserbelasting en de specifieke timeouts die in awaitPaint worden gebruikt) voordat event-data Google Tag Manager bereikt.
- Risico op dataverlies bij snel verlaten: Als een bezoeker een event triggert en de pagina vervolgens verlaat binnen dit microvertragingsvenster (voordat de uitgestelde dataLayer.push wordt uitgevoerd), wordt die specifieke event-data mogelijk niet verzonden. Voor kritieke events waar dit risico onaanvaardbaar is (bijv. direct voor een redirect of het verlaten van de pagina), kunnen alternatieve trackingmechanismen zoals navigator.sendBeacon worden overwogen voor die specifieke events, hoewel dit buiten de scope van de dataLayer.push-override valt.
Voor de meeste standaard analyse- en marketingtracking is deze lichte vertraging onbelangrijk en een waardevolle trade-off voor de significante verbetering in de door de gebruiker waargenomen prestaties en INP-scores. Echter, voor scenario's met ultralage latency (bijv. sommige soorten real-time biedingsinteracties die direct gekoppeld zijn aan GTM-events, of zeer gevoelige financiële dashboards waar precisie op millisecondeniveau bij event-logging cruciaal is), is deze aanpak mogelijk niet geschikt.
Anders weegt de winst in INP-prestaties en user experience doorgaans ruimschoots op tegen de minimale latency in dataverzameling.Anders weegt de winst in INP-prestaties en user experience doorgaans ruimschoots op tegen de minimale latency in dataverzameling.
Need your site lightning fast?
Join 500+ sites that now load faster and excel in Core Web Vitals.
- Fast on 1 or 2 sprints.
- 17+ years experience & over 500 fast sites
- Get fast and stay fast!

