Pianificazione degli eventi dataLayer per ottimizzare l'INP
Ritardare gli eventi GTM fino a quando il layout non si stabilizza per migliorare i valori INP

TL;DR: Migliorare l'INP ottimizzando Google Tag Manager
Il problema: Le chiamate standard di Google Tag Manager (dataLayer.push()), specialmente quando attivate immediatamente dalle interazioni dell'utente (come clic o tocchi), possono ritardare la capacità del browser di mostrare aggiornamenti visivi. Ciò influisce negativamente sul punteggio Interaction to Next Paint (INP) perché il browser è costretto a elaborare le attività GTM prima di eseguire il rendering del feedback visivo per quell'interazione.
La soluzione: Possiamo posticipare queste chiamate dataLayer.push() a dopo che il browser ha disegnato il frame successivo. Questo dà la priorità al rendering del feedback visivo immediato per l'utente. La correzione prevede un piccolo frammento JavaScript che modifica il comportamento predefinito di dataLayer.push() per incorporare questo ritardo.
Il vantaggio: Questo approccio si traduce comunemente in una riduzione da 20 ms a 100 ms dell'INP per i nostri clienti, spesso trasformando i punteggi Core Web Vitals insufficienti in punteggi sufficienti. Gli utenti sperimentano un'interfaccia notevolmente più reattiva. Sebbene la raccolta dei dati per GTM sia leggermente ritardata (in genere 50-250 ms), si tratta di un compromesso accettabile per la maggior parte degli scopi di analisi e marketing.
Affrontare le sfide INP causate dall'esecuzione di Google Tag Manager
Per uno dei nostri clienti, abbiamo osservato una riduzione di 100 ms della metrica Interaction to Next Paint (INP) semplicemente riprogrammando il momento in cui la funzione dataLayer.push() viene eseguita dopo un'interazione dell'utente. Questo miglioramento è stato ottenuto utilizzando un sostituto JavaScript "drop-in" facile da applicare e testare che dà priorità al rendering.
Table of Contents!
- TL;DR: Migliorare l'INP ottimizzando Google Tag Manager
- Il problema INP con dataLayer.push()
- La soluzione, dai priorità al paint, quindi invia al dataLayer!
- Applicazione del codice
- Test semplificati: Sovrascrittura Globale
- Perché questo aiuta l'Interaction to Next Paint
- Perché non usare semplicemente un ritardo fisso, un callback idle o uno scheduler?
- Compromessi
Il problema INP con dataLayer.push()
Se hai lavorato con Google Tag Manager (GTM), avrai familiarità con dataLayer.push(). È il metodo standard per inviare dati o eventi al Data Layer, consentendo l'attivazione dei tag. È ampiamente utilizzato, profondamente integrato in molte funzionalità del sito e le sue implicazioni in termini di prestazioni sono raramente messe in discussione. Tuttavia, presumere che venga sempre eseguito nel momento ottimale per l'esperienza dell'utente può essere problematico.
Quando dataLayer.push() viene chiamato direttamente all'interno di un gestore eventi per un'interazione dell'utente (ad esempio, il clic su un pulsante), in genere viene eseguito in modo sincrono. Ciò significa che tutti i tag GTM configurati per attivarsi in base a quell'evento tenteranno anche di essere eseguiti immediatamente e bloccheranno il thread principale prima di un aggiornamento del layout. Questo blocco impedisce al browser di eseguire rapidamente il rendering delle modifiche visive previste dall'interazione dell'utente (ad esempio, l'apertura di un menu, la visualizzazione di un indicatore di caricamento), portando a un punteggio INP scadente.
Esaminiamo cosa succede. La traccia delle prestazioni di seguito, proveniente da un importante sito web di notizie, evidenzia l'attività correlata a GTM in seguito a un'interazione dell'utente. In questo caso, le attività GTM hanno impiegato circa 90 ms per essere eseguite e con un valore INP complessivo di 263 ms questa interazione non supera questi Core Web Vitals!

La soluzione, dai priorità al paint, quindi invia al dataLayer!
La soluzione è semplice ed elegante, in linea con le migliori pratiche di ottimizzazione dell'INP: dare priorità alla percezione di velocità dell'utente. Invece di eseguire tutto il codice (gestione delle interazioni, aggiornamenti visivi e tracciamento GTM) in modo sincrono, dovremmo:
- Eseguire immediatamente il codice critico per l'aggiornamento visivo.
- Consentire al browser di disegnare queste modifiche visive.
- Quindi, eseguire il codice meno critico, come l'invio di eventi al dataLayer.
Questo approccio è spesso chiamato "cedere il controllo al thread principale". Vediamo l'impatto quando applichiamo questo pattern di yielding alle chiamate dataLayer.push() sullo stesso sito e per la stessa identica interazione di prima. L'unica differenza è che abbiamo programmato che dataLayer.push() avvenga dopo che il browser ha avuto la possibilità di eseguire il rendering del frame successivo utilizzando requestAnimationFrame.

Come puoi vedere, la stessa interazione che prima falliva ora supera agevolmente i Core Web Vitals. Tutti i dati necessari vengono ancora inviati al dataLayer. La differenza cruciale è che gli script relativi a GTM vengono ora eseguiti dopo che il browser ha aggiornato il layout in risposta all'azione dell'utente. Ciò significa che il tuo visitatore ottiene un feedback visivo immediato, migliorando la sua esperienza, anziché aspettare che gli script di tracciamento vengano elaborati.
Applicazione del codice
Il codice funziona sovrascrivendo la funzione dataLayer.push() predefinita con una funzione modificata che invia i dati al dataLayer dopo che è stato eseguito un aggiornamento del layout.
Funzione di supporto Await Paint
Questa funzione di supporto utilizza requestAnimationFrame per programmare l'esecuzione di una callback dopo che il browser ha disegnato il frame successivo.
// --- 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();
}
}Esempio di implementazione
Questo è un esempio di una funzione di utilità React che programma automaticamente il push al dataLayer.
export const pushToDataLayer = (event: string, data: Record<string, any> = {}): void => {
// Ensure dataLayer exists
if (typeof window !== 'undefined') {
window.dataLayer = window.dataLayer || [];
// wait for paint
awaitPaint(() => {
// Push event and data to dataLayer
window.dataLayer.push({
event,
...data,
timestamp: new Date().toISOString()
});
});
}
};
// Usage in a React component:
// import { useState, useEffect } from 'react';
// import { pushToDataLayer } from '../utils/analytics';
// function ProductCard({ product }) {
// const [isWishlisted, setIsWishlisted] = useState(false);
// // Track wishlist changes
// 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 ? '\u2665' : '\u2661'} {isWishlisted ? 'Wishlisted' : 'Add to Wishlist'}
// </button>
// </div>
// );
// }
Test semplificati: Sovrascrittura Globale
Per testare rapidamente questo pattern su tutto il sito o per implementazioni dataLayer.push() esistenti senza eseguire il refactoring di ciascuna, è possibile sovrascrivere a livello globale la funzione dataLayer.push().
Importante: Posiziona questo script in alto nella sezione <head> del tuo HTML, subito dopo aver caricato lo script del contenitore GTM. Questo assicura che la sovrascrittura sia in atto il prima possibile.
<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>
Perché questo aiuta l'Interaction to Next Paint
L'INP misura la latenza da un'interazione dell'utente (ad esempio, clic, tocco, pressione di un tasto) fino a quando il browser non disegna il successivo aggiornamento visivo in risposta a tale interazione. Se si eseguono in modo sincrono attività ad uso intensivo di risorse, come l'elaborazione degli eventi GTM e l'attivazione dei tag, immediatamente dopo un'interazione, si blocca il thread principale del browser. Ciò impedisce il rendering del feedback visivo che l'utente si aspetta. Ritardando l'esecuzione non critica di JavaScript come il tracciamento GTM a dopo che il browser ha disegnato gli aggiornamenti visivi, questo pattern garantisce agli utenti di ricevere un rapido feedback visivo, migliorando significativamente il punteggio INP.
Perché non usare semplicemente un ritardo fisso, un callback idle o uno scheduler?
- Ritardo fisso
setTimeout(delay): L'utilizzo di un ritardo preimpostato (ad es. setTimeout(..., 100)) è essenzialmente indovinare quando il rendering sarà completo. Non è adattivo; potrebbe essere troppo lungo (ritardando inutilmente il tracciamento) o troppo breve (bloccando ancora il paint). requestIdleCallback: Questa API pianifica il lavoro quando il browser è inattivo. Sebbene sia utile per le attività in background, non garantisce l'esecuzione tempestiva dopo l'aggiornamento visivo di una specifica interazione. Il callback potrebbe essere eseguito molto più tardi o, durante i periodi di punta, non essere eseguito affatto prima che l'utente esca dalla pagina.- Scheduler generici (
postTaskecc.): Mentre lo schedulerpostTaskdel browser offre la definizione delle priorità,requestAnimationFrameè specificamente legato al ciclo di vita del rendering. La funzione di supportoawaitPaintsfrutta questo aspetto utilizzandorequestAnimationFramecome segnale che il browser si sta preparando per il paint, e quindi aggiunge un ritardo minimo per l'attivazione dopo che il paint è probabilmente completo.
Compromessi
Ogni ottimizzazione ha potenziali compromessi.
- Micro-ritardo nella raccolta dei dati: Questa tecnica introduce un micro-ritardo prevedibile (circa 50-250 ms, a seconda del carico del browser e degli specifici timeout utilizzati in awaitPaint) prima che i dati dell'evento raggiungano Google Tag Manager.
- Rischio di perdita di dati in caso di uscita rapida: Se un visitatore attiva un evento e poi abbandona la pagina all'interno di questa finestra di micro-ritardo (prima che venga eseguito il dataLayer.push posticipato), quei dati specifici dell'evento potrebbero non essere inviati. Per gli eventi critici in cui questo rischio è inaccettabile (ad esempio, immediatamente prima di un reindirizzamento o dello scaricamento della pagina), potrebbero essere presi in considerazione meccanismi di tracciamento alternativi come navigator.sendBeacon per quegli eventi specifici, sebbene ciò esuli dall'ambito della sovrascrittura di dataLayer.push.
Per la maggior parte dei tracciamenti standard di analisi e marketing, questo leggero ritardo è irrilevante ed è un compromesso utile per il significativo miglioramento delle prestazioni percepite dall'utente e dei punteggi INP. Tuttavia, per scenari a bassissima latenza (ad esempio, alcuni tipi di interazioni di offerte in tempo reale (real-time bidding) direttamente collegate a eventi GTM, o dashboard finanziarie altamente sensibili in cui la precisione al millisecondo nella registrazione degli eventi è fondamentale), questo approccio potrebbe non essere adatto.
Altrimenti, il guadagno in termini di prestazioni INP ed esperienza utente in genere supera di gran lunga la latenza minima di raccolta dei dati.Altrimenti, il guadagno in termini di prestazioni INP ed esperienza utente in genere supera di gran lunga la latenza minima di raccolta dei dati.
Pinpoint the route, device, and connection that fails.
CoreDash segments every metric by route, device class, browser, and connection type. Real time data. Not the 28 day average Google gives you.
Explore Segmentation
