Schemaläggning av dataLayer-händelser för att optimera INP

Fördröjning av GTM-händelser tills layouten stabiliseras för förbättrade INP-värden

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2025-07-14

TL;DR: Förbättra INP genom att optimera Google Tag Manager

Problemet: Standardanrop till Google Tag Manager (dataLayer.push()), särskilt när de triggas omedelbart av användarinteraktioner (som klick eller tryckningar), kan fördröja webbläsarens förmåga att visa visuella uppdateringar. Detta påverkar Interaction to Next Paint (INP)-poängen negativt eftersom webbläsaren tvingas bearbeta GTM-uppgifter innan den kan rendera den visuella återkopplingen för den interaktionen.

Lösningen: Vi kan fördröja dessa dataLayer.push()-anrop tills efter att webbläsaren har målat nästa bildruta. Detta prioriterar rendering av omedelbar visuell återkoppling för användaren. Lösningen involverar ett litet JavaScript-kodfragment som modifierar standardbeteendet för dataLayer.push() för att inkorporera denna fördröjning.

Fördelen: Denna metod resulterar vanligtvis i en minskning av INP med 20ms till 100ms för våra kunder, och omvandlar ofta underkända Core Web Vitals-poäng till godkända. Användare upplever ett märkbart snabbare gränssnitt. Även om datainsamlingen för GTM fördröjs något (vanligtvis 50-250ms) är detta en acceptabel avvägning för de flesta analys- och marknadsföringsändamål.

Hantering av INP-utmaningar orsakade av Google Tag Manager-exekvering

För en av våra kunder observerade vi en minskning med 100ms i deras Interaction to Next Paint (INP)-mätvärde genom att helt enkelt schemalägga om när dataLayer.push()-funktionen exekveras efter en användarinteraktion. Denna förbättring uppnåddes med en enkel JavaScript-ersättning som prioriterar rendering.

INP-problemet med dataLayer.push() 

Om du har arbetat med Google Tag Manager (GTM) är du bekant med dataLayer.push(). Det är standardmetoden för att skicka data eller händelser till dataLayer, vilket möjliggör att taggar triggas. Den används i stor utsträckning, är djupt integrerad i många webbplatsfunktioner, och dess prestandapåverkan ifrågasätts sällan. Att anta att den alltid exekveras vid det optimala ögonblicket för user experience kan dock vara problematiskt.

När dataLayer.push() anropas direkt i en händelsehanterare för en användarinteraktion (t.ex. ett knappklick) exekveras den vanligtvis synkront. Det innebär att alla GTM-taggar som är konfigurerade att triggas baserat på den händelsen också kommer att försöka exekvera omedelbart och blockera huvudtråden innan en layoutuppdatering. Denna blockering hindrar webbläsaren från att snabbt rendera de visuella förändringar som förväntas av användarens interaktion (t.ex. öppna en meny, visa en laddningsindikator), vilket leder till en dålig INP-poäng.

Låt oss undersöka vad som händer. Prestandaspårningen nedan, från en stor nyhetswebbplats, belyser GTM-relaterad aktivitet efter en användarinteraktion. I det här fallet tog GTM-uppgifterna cirka 90ms att exekvera och med ett totalt INP-värde på 263ms klarar denna interaktion inte Core Web Vitals!

datalayer push inp

Lösningen, prioritera paint, skicka sedan till dataLayer!

Lösningen är både enkel och elegant och följer bästa praxis för INP-optimering: prioritera användarens upplevelse av hastighet. Istället för att exekvera all kod (interaktionshantering, visuella uppdateringar och GTM-spårning) synkront bör vi:

  1. Exekvera den kritiska koden för den visuella uppdateringen omedelbart.
  2. Låt webbläsaren måla dessa visuella förändringar.
  3. Exekvera sedan mindre kritisk kod, som att skicka händelser till dataLayer.

Denna metod kallas ofta "yielding to the main thread." Låt oss se effekten när vi tillämpar detta yielding-mönster på dataLayer.push()-anrop på samma webbplats och för exakt samma interaktion som tidigare. Den enda skillnaden är att vi har schemalagt dataLayer.push() att ske efter att webbläsaren har haft möjlighet att rendera nästa bildruta med requestAnimationFrame.

datalayer push inp yeilded

Som du kan se klarar samma interaktion som tidigare underkändes nu bekvämt Core Web Vitals. All nödvändig data skickas fortfarande till dataLayer. Den avgörande skillnaden är att GTM-relaterade skript nu exekveras efter att webbläsaren har uppdaterat layouten som svar på användarens åtgärd. Det innebär att din besökare får omedelbar visuell återkoppling, vilket förbättrar deras upplevelse, istället för att vänta på att spårningsskript ska bearbetas.

Tillämpa koden

Koden fungerar genom att åsidosätta standardfunktionen dataLayer.push() med en modifierad funktion som skickar data till dataLayer efter att en layoutuppdatering har genomförts.

Await Paint-hjälpfunktion

Denna hjälpfunktion använder requestAnimationFrame för att schemalägga en callback som körs efter att webbläsaren har målat nästa bildruta.

// --- INP Yield Pattern Implementation ---

// This helper ensures that a function only runs after the next paint (or safe fallback)
async function awaitPaint(fn) {
    await new Promise((resolve) => {
        // Fallback timeout: ensures we don't hang forever if RAF never fires
        setTimeout(resolve, 200); 

        // Request the next animation frame (signals readiness to paint)
        requestAnimationFrame(() => {
            // Small delay to ensure the frame is actually painted, not just queued
            setTimeout(resolve, 50);
        });
    });

    // Once the paint (or fallback) happens, run the provided function
    if (typeof fn === 'function') {
        fn();
    }
}

Implementeringsexempel

Detta är ett exempel på en React-hjälpfunktion som automatiskt schemalägger dataLayer push.

export const pushToDataLayer = (event: string, data: Record

Enkel testning: Global åsidosättning

För att snabbt testa detta mönster på hela din webbplats eller för befintliga dataLayer.push()-implementationer utan att behöva refaktorera var och en, kan du globalt åsidosätta dataLayer.push()-funktionen.

Viktigt: Placera detta skript högt i <head> i din HTML, omedelbart efter att GTM-containerskriptet har laddats. Detta säkerställer att din åsidosättning är på plats så snart som möjligt.

<script type="module">
    // Ensure dataLayer exists (standard GTM snippet part)
    window.dataLayer = window.dataLayer || [];

    // --- INP Yield Pattern Helper ---
    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);
            });
        });
    }

    // --- Applying the pattern to Google Tag Manager dataLayer.push globally ---
    if (window.dataLayer && typeof window.dataLayer.push === 'function') {
        // Preserve the original push function
        const originalDataLayerPush = window.dataLayer.push.bind(window.dataLayer);

        // Override dataLayer.push
        window.dataLayer.push = function (...args) {
            // Using an IIFE to use async/await syntax if preferred,
            // or directly call awaitPaint.
            (async () => {
                await awaitPaint(() => {
                    // Call the original push with its arguments after yielding to paint
                    originalDataLayerPush(...args);
                });
            })();
            // Return the value the original push would have, if any (though typically undefined)
            // For GTM, the push method doesn't have a meaningful return value for the caller.
            // The primary purpose is the side effect of adding to the queue.
        };
        console.log('dataLayer.push has been overridden to improve INP.');
    }
</script>

Varför detta hjälper Interaction to Next Paint

INP mäter fördröjningen från en användarinteraktion (t.ex. klick, tryck, knapptryckning) tills webbläsaren målar nästa visuella uppdatering som svar på den interaktionen. Om du synkront exekverar resurskrävande uppgifter, som GTM-händelsebearbetning och taggutlösning, omedelbart efter en interaktion blockerar du webbläsarens huvudtråd. Detta förhindrar rendering av den visuella återkopplingen som användaren förväntar sig. Genom att fördröja icke-kritisk JavaScript-exekvering som GTM-spårning tills efter att webbläsaren har målat de visuella uppdateringarna, säkerställer detta mönster att användare får snabb visuell återkoppling, vilket avsevärt förbättrar INP-poängen.

Varför inte bara använda en fast fördröjning, idle Callback eller Scheduler?

  • Fast setTimeout(delay): Att använda en hårdkodad fördröjning (t.ex. setTimeout(..., 100)) är i princip att gissa när renderingen kommer att slutföras. Det är inte adaptivt; det kan vara för långt (fördröjer spårning i onödan) eller för kort (blockerar fortfarande paint).
  • requestIdleCallback: Detta API schemalägger arbete när webbläsaren är inaktiv. Även om det är användbart för bakgrundsuppgifter garanterar det inte exekvering omedelbart efter en specifik interaktions visuella uppdatering. Callbacken kan köras mycket senare eller, under intensiva perioder, inte alls innan användaren navigerar bort.
  • Generella schemaläggare (postTask etc.): Medan webbläsarens postTask-schemaläggare erbjuder prioritering är requestAnimationFrame specifikt kopplad till renderingslivscykeln. awaitPaint-hjälparen utnyttjar detta genom att använda requestAnimationFrame som en signal att webbläsaren förbereder sig för att måla, och lägger sedan till en minimal fördröjning för att triggas efter att den målningen sannolikt är klar.

Avvägningar

Varje optimering har potentiella avvägningar.

  • Mikrofördröjning i datainsamling: Denna teknik introducerar en förutsägbar mikrofördröjning (ungefär 50-250ms, beroende på webbläsarbelastning och de specifika timeouts som används i awaitPaint) innan händelsedata når Google Tag Manager.
  • Risk för dataförlust vid snabb navigering: Om en besökare triggar en händelse och sedan lämnar sidan inom detta mikrofördröjningsfönster (innan den fördröjda dataLayer.push exekveras) kan den specifika händelsedatan möjligen inte skickas. För kritiska händelser där denna risk är oacceptabel (t.ex. omedelbart före en omdirigering eller sidlämning) kan alternativa spårningsmekanismer som navigator.sendBeacon övervägas för dessa specifika händelser, även om detta ligger utanför räckvidden för dataLayer.push-åsidosättningen.

För de flesta standardanalyser och marknadsföringsspårning är denna lilla fördröjning obetydlig och en värdefull avvägning för den betydande förbättringen av användarupplevd prestanda och INP-poäng. Men för scenarier med ultralåg latens (t.ex. vissa typer av realtidsbudgivningsinteraktioner direkt kopplade till GTM-händelser, eller mycket känsliga finansiella instrumentpaneler där millisekundprecision i händelseloggning är avgörande) kanske denna metod inte är lämplig.

I övrigt överväger förbättringen i INP-prestanda och user experience vanligtvis vida den minimala fördröjningen i datainsamling.I övrigt överväger förbättringen i INP-prestanda och user experience vanligtvis vida den minimala fördröjningen i datainsamling.

Performance is a Feature.

Treating speed as an afterthought fails. Build a performance culture with a dedicated 2-sprint optimization overhaul.

Initialize Project >>

  • 2-Sprint Overhaul
  • Culture Building
  • Sustainable Speed
Schemaläggning av dataLayer-händelser för att optimera INPCore Web Vitals Schemaläggning av dataLayer-händelser för att optimera INP