Programmare gli eventi dataLayer per ottimizzare l'INP
Come ritardare gli eventi GTM fino a dopo il paint del browser migliora i tuoi punteggi INP

TL;DR: Migliorare l'INP ritardando dataLayer.push()
Quando dataLayer.push() viene eseguito in modo sincrono all'interno di un gestore di eventi, GTM elabora i tag prima che il browser possa eseguire il paint del feedback visivo. Questo blocca il thread principale e gonfia il tuo Interaction to Next Paint (INP). La soluzione: programmare l'esecuzione di dataLayer.push() dopo che il browser ha eseguito il paint del frame successivo utilizzando requestAnimationFrame. Questo pattern taglia da 20 a 100 ms dall'INP sulla maggior parte dei siti, spesso trasformando un punteggio insufficiente in uno sufficiente. La raccolta dei dati viene ritardata da 50 a 250 ms, il che è irrilevante per il tracciamento analitico e di marketing.
Come dataLayer.push() danneggia il tuo INP
Per uno dei nostri clienti, abbiamo misurato una riduzione di 100 ms nell'Interaction to Next Paint (INP) riprogrammando il momento in cui dataLayer.push() viene eseguito dopo un'interazione dell'utente. La soluzione è un sostituto JavaScript pronto all'uso che dà la priorità al rendering rispetto al tracciamento.
Ultima revisione da parte di Arjen Karel a marzo 2026
Table of Contents!
- TL;DR: Migliorare l'INP ritardando dataLayer.push()
- Come dataLayer.push() danneggia il tuo INP
- Il problema con i dataLayer.push() sincroni
- La soluzione: prima il paint, poi il push
- L'helper awaitPaint
- Sovrascrittura globale per test rapidi
- Perché questo migliora l'INP
- Perché non un ritardo fisso, un Idle Callback o scheduler.yield()?
- Compromessi
Il problema con i dataLayer.push() sincroni
Se lavori con Google Tag Manager, conosci dataLayer.push(). È il metodo standard per inviare eventi al Data Layer ed è profondamente integrato nella maggior parte delle funzionalità dei siti. Il problema è che nessuno si interroga su quando viene eseguito.
Quando dataLayer.push() viene chiamato direttamente all'interno di un gestore di eventi (un clic su un pulsante, l'invio di un modulo, l'attivazione di un menu), viene eseguito in modo sincrono. Anche i tag GTM configurati per attivarsi in base a quell'evento vengono eseguiti immediatamente, bloccando il thread principale prima che il browser possa aggiornare il layout. Il visitatore fa clic su un pulsante e non vede accadere nulla mentre GTM elabora gli script di tracciamento in background. Quel ritardo è il tuo ritardo di presentazione, ed è il principale responsabile di un INP scarso.
La traccia delle prestazioni di seguito, proveniente da un importante sito web di notizie, mostra l'attività di GTM a seguito di un'interazione dell'utente. I task di GTM hanno impiegato circa 90 ms per l'esecuzione, spingendo l'INP complessivo a 263 ms. Quell'interazione fallisce i Core Web Vitals.

La soluzione: prima il paint, poi il push
La soluzione è in linea con la guida all'ottimizzazione dell'INP di web.dev: fare yield al thread principale in modo che il browser possa eseguire il paint prima di eseguire codice non critico. Invece di eseguire il tracciamento in modo sincrono all'interno del gestore di eventi:
- Esegui immediatamente il codice per l'aggiornamento visivo.
- Lascia che il browser esegua il paint.
- Quindi esegui il push al dataLayer.
Ecco la stessa interazione dopo aver applicato questo pattern. L'unico cambiamento è che dataLayer.push() ora viene eseguito dopo che il browser ha eseguito il paint del frame successivo.

L'interazione che in precedenza falliva ora viene superata comodamente. Tutti i dati raggiungono comunque il dataLayer. La differenza è che gli script GTM vengono eseguiti dopo che il browser ha risposto all'azione dell'utente, non prima.
L'helper awaitPaint
Questo helper utilizza requestAnimationFrame per programmare una callback dopo che il browser ha eseguito il paint. Il setTimeout annidato assicura che la funzione venga eseguita dopo il completamento del paint, non appena prima di esso. Questo è il pattern consigliato da web.dev per posticipare il lavoro non critico dopo un'interazione.
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: ensures we don't hang if RAF never fires (e.g. background tabs)
setTimeout(resolve, 200);
// Schedule after the next paint
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
Utilizzo dell'helper
Avvolgi qualsiasi chiamata a dataLayer.push() in awaitPaint per ritardarla fino a dopo il paint:
function pushToDataLayer(event, data) {
window.dataLayer = window.dataLayer || [];
awaitPaint(() => {
window.dataLayer.push({
event,
...data,
timestamp: new Date().toISOString()
});
});
}
// Usage
document.querySelector('.buy-button').addEventListener('click', () => {
// Visual feedback happens immediately
showLoadingSpinner();
// Tracking fires after paint
pushToDataLayer('purchase_click', { productId: '123' });
});
Sovrascrittura globale per test rapidi
Per testare questo pattern sull'intero sito senza dover eseguire il refactoring di ogni chiamata dataLayer.push(), puoi sovrascrivere globalmente la funzione di push. Inserisci questo script nell'<head> immediatamente dopo lo snippet del contenitore GTM.
<script type="module">
window.dataLayer = window.dataLayer || [];
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);
});
});
}
if (window.dataLayer && typeof window.dataLayer.push === 'function') {
const originalPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function (...args) {
(async () => {
await awaitPaint(() => {
originalPush(...args);
});
})();
};
}
</script>
Questo sovrascrive ogni dataLayer.push() sulla pagina. Tutti i trigger di eventi GTM esistenti, le chiamate di tracciamento hardcoded e le integrazioni di plugin ne traggono vantaggio automaticamente senza alcuna modifica al codice.
Perché questo migliora l'INP
L'INP misura il tempo da un'interazione dell'utente fino a quando il browser esegue il paint della risposta visiva. Se esegui in modo sincrono l'elaborazione degli eventi GTM all'interno di un gestore di eventi, blocchi il thread principale e impedisci il rendering. Il Web Almanac 2024 ha rilevato che il ritardo di presentazione è il maggiore contributore a un INP scarso alla mediana e gli script di tracciamento sono tra le cause principali.
I numeri lo confermano. Il Web Almanac 2025 mostra che il 77% delle pagine mobili supera l'INP complessivamente, ma solo il 63% dei 1.000 siti più visitati lo fa. Quei siti principali caricano più script di terze parti. Su Subito (il più grande marketplace di annunci in Italia), disabilitare un singolo script di tracciamento TikTok caricato tramite GTM ha ridotto l'INP da 208 ms a circa 170 ms. Uno script, 38 ms risparmiati.
Ritardando dataLayer.push() fino a dopo il paint, sposti tutta l'elaborazione di GTM fuori dal percorso critico dell'interazione. L'utente riceve immediatamente un feedback visivo. GTM si attiva comunque, solo da 50 a 250 ms dopo.
Perché non un ritardo fisso, un Idle Callback o scheduler.yield()?
setTimeout(delay)fisso: Un ritardo hardcoded (ad es.setTimeout(..., 100)) è un'ipotesi su quando verrà completato il rendering. Troppo lungo e ritardi inutilmente il tracciamento. Troppo corto e blocchi comunque il paint.requestIdleCallback: Programma il lavoro quando il browser è inattivo, ma non garantisce un'esecuzione tempestiva dopo un'interazione specifica. La callback potrebbe essere eseguita molto più tardi, o per niente prima che l'utente abbandoni la pagina.scheduler.yield(): La API moderna per fare yield al thread principale. Preserva la priorità del task (a differenza disetTimeoutche manda la tua continuazione in fondo alla coda). Tuttavia,scheduler.yield()non garantisce che si sia verificato un paint prima dell'esecuzione del tuo codice. Per questo caso d'uso specifico,requestAnimationFrameè lo strumento migliore perché è legato al ciclo di vita del rendering. Nota chescheduler.yield()non è ancora supportato in Safari.
La combinazione di requestAnimationFrame + setTimeout è progettata specificamente per questo scenario. requestAnimationFrame si attiva appena prima che il browser esegua il paint. Il setTimeout annidato crea un nuovo task che viene eseguito dopo il completamento di quel paint. Insieme garantiscono che il codice posticipato venga eseguito dopo l'aggiornamento visivo, che è esattamente ciò che misura l'INP.
Compromessi
- Micro-ritardo nella raccolta dei dati: Gli eventi raggiungono GTM da 50 a 250 ms più tardi rispetto al push sincrono. Per il tracciamento analitico e di marketing, questo è irrilevante.
- Perdita di dati in caso di uscita rapida: Se un visitatore attiva un evento e lascia la pagina entro la finestra di ritardo, quell'evento potrebbe non attivarsi. Per eventi critici prima di un reindirizzamento o dello scaricamento della pagina, usa
navigator.sendBeacon()invece didataLayer.push(). Consulta le motivazioni per limitare gli script di analisi per maggiori informazioni sull'API Beacon.
Per la maggior parte dei tracciamenti, il guadagno in INP supera di gran lunga il minimo ritardo. Se hai bisogno di precisione al millisecondo nella registrazione degli eventi (real-time bidding, dashboard finanziarie), questo approccio non è adatto. Per tutto il resto, è una vittoria netta.
Dopo aver applicato l'override, verifica il miglioramento con un Real User Monitoring. I test di laboratorio mostreranno la differenza, ma i dati sul campo provenienti da utenti reali su reti reali sono ciò che conta per i tuoi punteggi Core Web Vitals.
Performance degrades unless you guard it.
I do not just fix the metrics. I set up the monitoring, the budgets, and the processes so your team keeps them green after I leave.
Start the Engagement
