Planung von dataLayer-Events zur Optimierung des INP

Verzögerung von GTM-Events, bis sich das Layout stabilisiert, für verbesserte INP-Werte

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

TL;DR: Verbesserung des INP durch Optimierung des Google Tag Manager

Das Problem: Standard Google Tag Manager (dataLayer.push()) Aufrufe, besonders wenn sie sofort durch Benutzerinteraktionen (wie Klicks oder Taps) ausgelöst werden, können die Fähigkeit des Browsers, visuelle Updates anzuzeigen, verzögern. Dies wirkt sich negativ auf den Interaction to Next Paint (INP) Score aus, da der Browser gezwungen ist, GTM-Aufgaben zu verarbeiten, bevor er das visuelle Feedback für diese Interaktion rendert.

Die Lösung: Wir können diese dataLayer.push() Aufrufe verzögern, bis der Browser den nächsten Frame gemalt hat. Dies priorisiert das Rendern von sofortigem visuellen Feedback für den Benutzer. Der Fix beinhaltet ein kleines JavaScript-Snippet, das das Standardverhalten von dataLayer.push() modifiziert, um diese Verzögerung einzubeziehen.

Der Nutzen: Dieser Ansatz führt üblicherweise zu einer Reduzierung des INP um 20ms bis 100ms für unsere Kunden, was oft dazu führt, dass nicht bestandene Core Web Vitals Scores bestanden werden. Benutzer erleben eine spürbar snappiere Oberfläche. Während die Datenerfassung für GTM leicht verzögert ist (typischerweise 50-250ms), ist dies für die meisten Analyse- und Marketingzwecke ein akzeptabler Kompromiss.

Adressierung von INP-Herausforderungen durch Google Tag Manager Ausführung

Für einen unserer Kunden beobachteten wir eine Reduzierung von 100ms in ihrer Interaction to Next Paint (INP) Metrik, indem wir einfach neu planten, wann die dataLayer.push() Funktion nach einer Benutzerinteraktion ausgeführt wird. Diese Verbesserung wurde durch einen einfach anzuwendenden und zu testenden JavaScript "Drop-In" Ersatz erreicht, der das Rendern priorisiert.

Das INP-Problem mit dataLayer.push() 

Wenn Sie mit Google Tag Manager (GTM) gearbeitet haben, sind Sie mit dataLayer.push() vertraut. Es ist die Standardmethode zum Senden von Daten oder Events an den Data Layer, wodurch Tags ausgelöst werden können. Es ist weit verbreitet, tief in viele Website-Funktionalitäten integriert und seine Leistungsauswirkungen werden selten in Frage gestellt. Jedoch kann die Annahme, dass es immer zum optimalen Zeitpunkt für die Benutzererfahrung ausgeführt wird, problematisch sein.

Wenn dataLayer.push() direkt innerhalb eines Event-Handlers für eine Benutzerinteraktion (z. B. einen Button-Klick) aufgerufen wird, wird es typischerweise synchron ausgeführt. Das bedeutet, dass alle GTM-Tags, die so konfiguriert sind, dass sie basierend auf diesem Event ausgelöst werden, ebenfalls versuchen werden, sofort ausgeführt zu werden und den Main-Thread vor einer Layout-Aktualisierung blockieren. Diese Blockierung verhindert, dass der Browser die von der Benutzerinteraktion erwarteten visuellen Änderungen schnell rendert (z. B. Öffnen eines Menüs, Anzeigen eines Lade-Spinners), was zu einem schlechten INP-Score führt.

Lassen Sie uns untersuchen, was passiert. Der Performance-Trace unten, von einer großen Nachrichten-Website, hebt GTM-bezogene Aktivitäten nach einer Benutzerinteraktion hervor. In diesem Fall dauerten die GTM-Aufgaben ungefähr 90ms zur Ausführung und mit einem Gesamt-INP-Wert von 263ms besteht diese Interaktion die Core Web Vitals nicht!

datalayer push inp

Die Lösung, Paint priorisieren, dann in den Datalayer pushen!

Die Lösung ist sowohl einfach als auch elegant und entspricht den Best Practices zur INP-Optimierung: Priorisieren Sie die Wahrnehmung von Geschwindigkeit durch den Benutzer. Anstatt allen Code (Benutzerinteraktionsbehandlung, visuelle Updates und GTM-Tracking) synchron auszuführen, sollten wir:

  1. Den kritischen Code für das visuelle Update sofort ausführen.
  2. Dem Browser erlauben, diese visuellen Änderungen zu malen.
  3. Dann weniger kritischen Code ausführen, wie das Pushen von Events in den dataLayer.

Dieser Ansatz wird oft als "Yielding to the Main Thread" bezeichnet. Lassen Sie uns die Auswirkungen sehen, wenn wir dieses Yielding-Muster auf dataLayer.push() Aufrufe auf derselben Website und für exakt dieselbe Interaktion wie zuvor anwenden. Der einzige Unterschied ist, dass wir den dataLayer.push() so geplant haben, dass er auftritt, nachdem der Browser eine Chance hatte, den nächsten Frame mit requestAnimationFrame zu rendern.

datalayer push inp yeilded

Wie Sie sehen können, besteht dieselbe Interaktion, die zuvor fehlgeschlagen ist, nun die Core Web Vitals bequem. Alle notwendigen Daten werden immer noch an den dataLayer gesendet. Der entscheidende Unterschied ist, dass GTM-bezogene Skripte nun ausgeführt werden, nachdem der Browser das Layout als Reaktion auf die Aktion des Benutzers aktualisiert hat. Das bedeutet, dass Ihr Besucher sofortiges visuelles Feedback erhält, was seine Erfahrung verbessert, anstatt darauf zu warten, dass Tracking-Skripte verarbeitet werden.

Anwenden des Codes

Der Code funktioniert, indem er die Standard dataLayer.push() Funktion mit einer modifizierten Funktion überschreibt, die die Daten in den dataLayer pusht, nach dem eine Layout-Aktualisierung durchgeführt wurde.

Await Paint Hilfsfunktion

Diese Hilfsfunktion verwendet requestAnimationFrame, um einen Callback zu planen, der ausgeführt wird, nachdem der Browser den nächsten Frame gemalt hat.

// --- INP Yield Pattern Implementierung ---

// Dieser Helfer stellt sicher, dass eine Funktion nur nach dem nächsten Paint (oder sicherem Fallback) ausgeführt wird
async function awaitPaint(fn) {
    await new Promise((resolve) => {
        // Fallback-Timeout: stellt sicher, dass wir nicht ewig hängen, wenn RAF nie feuert
        setTimeout(resolve, 200); 

        // Den nächsten Animations-Frame anfordern (signalisiert Bereitschaft zum Malen)
        requestAnimationFrame(() => {
            // Kleine Verzögerung, um sicherzustellen, dass der Frame tatsächlich gemalt wird, nicht nur in der Warteschlange steht
            setTimeout(resolve, 50);
        });
    });

    // Sobald das Paint (oder Fallback) passiert, die bereitgestellte Funktion ausführen
    if (typeof fn === 'function') {
        fn();
    }
}

Implementierungsbeispiel

Dies ist ein Beispiel für eine React Utility-Funktion, die den dataLayer-Push automatisch plant.

export const pushToDataLayer = (event: string, data: Record<string, any> = {}): void => {
  // Sicherstellen, dass dataLayer existiert
  if (typeof window !== 'undefined') {
    window.dataLayer = window.dataLayer || [];
    
    // Auf Paint warten
    awaitPaint(() => {
        // Event und Daten in dataLayer pushen
        window.dataLayer.push({
          event,
          ...data,
          timestamp: new Date().toISOString()
        });
    });
  }
};

// Verwendung in einer React-Komponente:
// import { useState, useEffect } from 'react';
// import { pushToDataLayer } from '../utils/analytics';

// function ProductCard({ product }) {
//   const [isWishlisted, setIsWishlisted] = useState(false);
  
//   // Wishlist-Änderungen tracken
//   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 ? 'Wunschliste' : 'Zur Wunschliste hinzufügen'}
//       </button>
//     </div>
//   );
// }

Testen leicht gemacht: Globaler Override

Um dieses Muster schnell auf Ihrer gesamten Website oder für vorhandene dataLayer.push() Implementierungen zu testen, ohne jede einzelne neu zu faktorisieren, können Sie die dataLayer.push() Funktion global überschreiben.

Wichtig: Platzieren Sie dieses Skript weit oben im <head> Ihres HTML, unmittelbar nachdem das GTM-Container-Skript geladen wurde. Dies stellt sicher, dass Ihr Override so schnell wie möglich aktiv ist.

<script type="module">
    // Sicherstellen, dass dataLayer existiert (Standard GTM Snippet Teil)
    window.dataLayer = window.dataLayer || [];

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

    // --- Anwenden des Musters auf Google Tag Manager dataLayer.push global ---
    if (window.dataLayer && typeof window.dataLayer.push === 'function') {
        // Die ursprüngliche Push-Funktion bewahren
        const originalDataLayerPush = window.dataLayer.push.bind(window.dataLayer);

        // dataLayer.push überschreiben
        window.dataLayer.push = function (...args) {
            // Verwendung einer IIFE, um async/await Syntax zu verwenden, wenn bevorzugt,
            // oder direkt awaitPaint aufrufen.
            (async () => {
                await awaitPaint(() => {
                    // Den ursprünglichen Push mit seinen Argumenten aufrufen nach Yielding zu Paint
                    originalDataLayerPush(...args);
                });
            })();
            // Den Wert zurückgeben, den der ursprüngliche Push haben würde, falls vorhanden (obwohl typischerweise undefined)
            // Für GTM hat die Push-Methode keinen sinnvollen Rückgabewert für den Aufrufer.
            // Der primäre Zweck ist der Nebeneffekt des Hinzufügens zur Warteschlange.
        };
        console.log('dataLayer.push has been overridden to improve INP.');
    }
</script>

Warum dies dem Interaction to Next Paint hilft

INP misst die Latenz von einer Benutzerinteraktion (z. B. Klick, Tap, Tastendruck), bis der Browser das nächste visuelle Update als Reaktion auf diese Interaktion malt. Wenn Sie ressourcenintensive Aufgaben, wie GTM-Event-Verarbeitung und Tag-Firing, synchron unmittelbar nach einer Interaktion ausführen, blockieren Sie den Main-Thread des Browsers. Dies verhindert das Rendern des visuellen Feedbacks, das der Benutzer erwartet. Durch das Aufschieben der Ausführung nicht kritischen JavaScripts wie GTM-Tracking, bis der Browser die visuellen Updates gemalt hat, stellt dieses Muster sicher, dass Benutzer schnelles visuelles Feedback erhalten, was den INP-Score signifikant verbessert.

Warum nicht einfach eine feste Verzögerung, Idle Callback oder Scheduler verwenden?

  • Feste setTimeout(delay): Die Verwendung einer fest codierten Verzögerung (z. B. setTimeout(..., 100)) ist im Wesentlichen ein Raten, wann das Rendern abgeschlossen sein wird. Es ist nicht adaptiv; es könnte zu lang sein (Tracking unnötig verzögern) oder zu kurz (Paint immer noch blockieren).
  • requestIdleCallback: Diese API plant Arbeit, wenn der Browser im Leerlauf ist. Während nützlich für Hintergrundaufgaben, garantiert es keine Ausführung zeitnah nach dem visuellen Update einer spezifischen Interaktion. Der Callback könnte viel später laufen oder, während geschäftiger Perioden, gar nicht, bevor der Benutzer wegnavigiert.
  • Generische Scheduler (postTask usw.): Während der postTask Scheduler des Browsers Priorisierung bietet, ist requestAnimationFrame spezifisch an den Rendering-Lebenszyklus gebunden. Der awaitPaint Helfer nutzt dies, indem er requestAnimationFrame als Signal verwendet, dass der Browser sich auf das Malen vorbereitet, und fügt dann eine minimale Verzögerung hinzu, um zu feuern, nachdem dieses Malen wahrscheinlich abgeschlossen ist.

Kompromisse

Jede Optimierung hat potenzielle Kompromisse.

  • Mikro-Verzögerung in der Datenerfassung: Diese Technik führt eine vorhersagbare Mikro-Verzögerung (ungefähr 50-250ms, abhängig von der Browserlast und den spezifischen Timeouts, die in awaitPaint verwendet werden) ein, bevor Eventdaten Google Tag Manager erreichen.
  • Risiko von Datenverlust bei schnellem Exit: Wenn ein Besucher ein Event auslöst und dann die Seite innerhalb dieses Mikro-Verzögerungsfensters verlässt (bevor der verzögerte dataLayer.push ausgeführt wird), werden diese spezifischen Eventdaten möglicherweise nicht gesendet. Für kritische Events, bei denen dieses Risiko inakzeptabel ist (z. B. unmittelbar vor einer Weiterleitung oder dem Entladen der Seite), könnten alternative Tracking-Mechanismen wie navigator.sendBeacon für diese spezifischen Events in Betracht gezogen werden, obwohl dies außerhalb des Geltungsbereichs des dataLayer.push Overrides liegt.

Für die meisten Standardanalysen und Marketing-Trackings ist diese geringfügige Verzögerung belanglos und ein lohnender Kompromiss für die signifikante Verbesserung der vom Benutzer wahrgenommenen Leistung und der INP-Scores. Jedoch für Ultra-Low-Latency-Szenarien (z. B. einige Arten von Real-Time-Bidding-Interaktionen, die direkt mit GTM-Events verknüpft sind, oder hochsensible Finanz-Dashboards, wo Millisekunden-Präzision bei der Event-Protokollierung oberstes Gebot ist), ist dieser Ansatz möglicherweise nicht geeignet.

Ansonsten überwiegt der Gewinn an INP-Leistung und Benutzererfahrung typischerweise bei weitem die minimale Datenerfassungslatenz.Ansonsten überwiegt der Gewinn an INP-Leistung und Benutzererfahrung typischerweise bei weitem die minimale Datenerfassungslatenz.

Compare your segments.

Is iOS slower than Android? Is the checkout route failing INP? Filter by device, route, and connection type.

Analyze Segments >>

  • Device filtering
  • Route Analysis
  • Connection Types
Planung von dataLayer-Events zur Optimierung des INPCore Web Vitals Planung von dataLayer-Events zur Optimierung des INP