Planifier les événements dataLayer pour optimiser l'INP
Différer les événements GTM jusqu'à ce que la mise en page se stabilise pour des valeurs INP améliorées

TL;DR : Améliorer l'INP en optimisant Google Tag Manager
Le Problème : Les appels standard Google Tag Manager (dataLayer.push()), en particulier lorsqu'ils sont déclenchés immédiatement par des interactions utilisateur (comme des clics ou des tapes), peuvent retarder la capacité du navigateur à afficher des mises à jour visuelles. Cela impacte négativement le score Interaction to Next Paint (INP) car le navigateur est forcé de traiter les tâches GTM avant de rendre le retour visuel pour cette interaction.
La Solution : Nous pouvons différer ces appels dataLayer.push() jusqu'à ce que le navigateur ait peint la prochaine frame. Cela priorise le rendu du retour visuel immédiat pour l'utilisateur. La correction implique un petit snippet JavaScript qui modifie le comportement par défaut de dataLayer.push() pour incorporer ce report.
Le Bénéfice : Cette approche entraîne généralement une réduction de 20ms à 100ms de l'INP pour nos clients, transformant souvent des scores Core Web Vitals défaillants en scores passables. Les utilisateurs bénéficient d'une interface sensiblement plus réactive. Bien que la collecte de données pour GTM soit légèrement retardée (généralement 50-250ms), c'est un compromis acceptable pour la plupart des objectifs d'analyse et de marketing.
Relever les défis INP causés par l'exécution de Google Tag Manager
Pour l'un de nos clients, nous avons observé une réduction de 100ms de leur métrique Interaction to Next Paint (INP) en replanifiant simplement l'exécution de la fonction dataLayer.push() après une interaction utilisateur. Cette amélioration a été obtenue en utilisant un remplacement JavaScript "drop-in" facile à appliquer et à tester qui priorise le rendu.
Table of Contents!
- TL;DR : Améliorer l'INP en optimisant Google Tag Manager
- Le problème INP avec dataLayer.push()
- La solution, prioriser le paint, puis pousser vers le datalayer !
- Appliquer le Code
- Test Facilité : Remplacement Global
- Pourquoi cela aide l'Interaction to Next Paint
- Pourquoi ne pas simplement utiliser un délai fixe, idle Callback ou Scheduler ?
- Compromis
Le problème INP avec dataLayer.push()
Si vous avez travaillé avec Google Tag Manager (GTM), vous connaissez dataLayer.push(). C'est la méthode standard pour envoyer des données ou des événements au Data Layer, permettant aux balises de se déclencher. C'est largement utilisé, profondément intégré dans de nombreuses fonctionnalités du site, et ses implications sur la performance sont rarement remises en question. Cependant, supposer qu'il s'exécute toujours au moment optimal pour l'expérience utilisateur peut être problématique.
Lorsque dataLayer.push() est appelé directement dans un gestionnaire d'événements pour une interaction utilisateur (par exemple, un clic sur un bouton), il s'exécute généralement de manière synchrone. Cela signifie que toutes les balises GTM configurées pour se déclencher sur la base de cet événement tenteront également de s'exécuter immédiatement et de bloquer le thread principal avant une mise à jour de la mise en page. Ce blocage empêche le navigateur de rendre rapidement les changements visuels attendus de l'interaction de l'utilisateur (par exemple, ouvrir un menu, afficher une roue de chargement), conduisant à un mauvais score INP.
Examinons ce qui se passe. La trace de performance ci-dessous, provenant d'un grand site d'actualités, met en évidence l'activité liée à GTM suite à une interaction utilisateur. Dans ce cas, les tâches GTM ont pris environ 90ms pour s'exécuter et avec une valeur INP globale de 263ms, cette interaction échoue à ce Core Web Vitals !

La solution, prioriser le paint, puis pousser vers le datalayer !
La solution est à la fois simple et élégante, s'alignant sur les meilleures pratiques d'optimisation INP : prioriser la perception de vitesse de l'utilisateur. Au lieu d'exécuter tout le code (gestion de l'interaction, mises à jour visuelles et suivi GTM) de manière synchrone, nous devrions :
- Exécuter le code critique pour la mise à jour visuelle immédiatement.
- Permettre au navigateur de peindre ces changements visuels.
- Ensuite, exécuter le code moins critique, comme pousser les événements vers le dataLayer.
Cette approche est souvent appelée "céder au thread principal" (yielding to the main thread). Voyons l'impact lorsque nous appliquons ce modèle de cession aux appels dataLayer.push() sur le même site et pour exactement la même interaction qu'auparavant. La seule différence est que nous avons planifié le dataLayer.push() pour qu'il se produise après que le navigateur a eu la chance de rendre la prochaine frame en utilisant requestAnimationFrame.

Comme vous pouvez le voir, la même interaction qui échouait auparavant passe maintenant confortablement les Core Web Vitals. Toutes les données nécessaires sont toujours envoyées au dataLayer. La différence cruciale est que les scripts liés à GTM s'exécutent maintenant après que le navigateur a mis à jour la mise en page en réponse à l'action de l'utilisateur. Cela signifie que votre visiteur obtient un retour visuel immédiat, améliorant son expérience, plutôt que d'attendre que les scripts de suivi soient traités.
Appliquer le Code
Le code fonctionne en remplaçant la fonction dataLayer.push() par défaut par une fonction modifiée qui pousse les données vers le dataLayer après qu'une mise à jour de la mise en page a été effectuée.
Fonction helper Await Paint
Cette fonction helper utilise requestAnimationFrame pour planifier une callback à exécuter après que le navigateur a peint la prochaine frame.
// --- 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();
}
}Exemple d'implémentation
Ceci est un exemple de fonction utilitaire React qui planifie automatiquement le push 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 ? '♥' : '♡'} {isWishlisted ? 'Wishlisted' : 'Add to Wishlist'}
// </button>
// </div>
// );
// }
Test Facilité : Remplacement Global
Pour tester rapidement ce modèle sur l'ensemble de votre site ou pour les implémentations existantes de dataLayer.push() sans refactoriser chacune d'elles, vous pouvez remplacer globalement la fonction dataLayer.push().
Important : Placez ce script haut dans le <head> de votre HTML, immédiatement après le chargement du script du conteneur GTM. Cela garantit que votre remplacement est en place dès que possible.
<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>
Pourquoi cela aide l'Interaction to Next Paint
INP mesure la latence depuis une interaction utilisateur (par exemple, clic, tape, appui sur une touche) jusqu'à ce que le navigateur peigne la prochaine mise à jour visuelle en réponse à cette interaction. Si vous exécutez de manière synchrone des tâches gourmandes en ressources, telles que le traitement des événements GTM et le déclenchement des balises, immédiatement après une interaction, vous bloquez le thread principal du navigateur. Cela empêche le rendu du retour visuel que l'utilisateur attend. En différant l'exécution de JavaScript non critique comme le suivi GTM jusqu'à ce que le navigateur ait peint les mises à jour visuelles, ce modèle garantit que les utilisateurs reçoivent un retour visuel rapide, améliorant considérablement le score INP.
Pourquoi ne pas simplement utiliser un délai fixe, idle Callback ou Scheduler ?
setTimeout(delay)fixe : Utiliser un délai codé en dur (par exemple, setTimeout(..., 100)) revient essentiellement à deviner quand le rendu sera terminé. Ce n'est pas adaptatif ; cela pourrait être trop long (retardant inutilement le suivi) ou trop court (bloquant toujours le paint).requestIdleCallback: Cette API planifie le travail lorsque le navigateur est inactif. Bien qu'utile pour les tâches d'arrière-plan, elle ne garantit pas l'exécution rapidement après la mise à jour visuelle d'une interaction spécifique. La callback pourrait s'exécuter beaucoup plus tard ou, pendant les périodes chargées, pas du tout avant que l'utilisateur ne quitte la page.- Planificateurs génériques (
postTasketc.) : Bien que le planificateurpostTaskdu navigateur offre une priorisation,requestAnimationFrameest spécifiquement lié au cycle de vie du rendu. L'helperawaitPainttire parti de cela en utilisantrequestAnimationFramecomme signal que le navigateur se prépare à peindre, puis ajoute un délai minimal pour se déclencher après que ce paint soit probablement terminé.
Compromis
Chaque optimisation a des compromis potentiels.
- Micro-délai dans la collecte de données : Cette technique introduit un micro-délai prévisible (environ 50-250ms, selon la charge du navigateur et les timeouts spécifiques utilisés dans awaitPaint) avant que les données d'événement n'atteignent Google Tag Manager.
- Risque de perte de données lors d'une sortie rapide : Si un visiteur déclenche un événement puis quitte la page dans cette fenêtre de micro-délai (avant que le dataLayer.push différé ne s'exécute), ces données d'événement spécifiques pourraient ne pas être envoyées. Pour les événements critiques où ce risque est inacceptable (par exemple, immédiatement avant une redirection ou un déchargement de page), des mécanismes de suivi alternatifs comme navigator.sendBeacon pourraient être envisagés pour ces événements spécifiques, bien que cela soit hors de la portée du remplacement de dataLayer.push.
Pour la plupart des suivis analytiques et marketing standard, ce léger délai est sans conséquence et constitue un compromis valable pour l'amélioration significative de la performance perçue par l'utilisateur et des scores INP. Cependant, pour les scénarios à ultra-faible latence (par exemple, certains types d'interactions d'enchères en temps réel directement liées aux événements GTM, ou des tableaux de bord financiers hautement sensibles où la précision à la milliseconde dans la journalisation des événements est primordiale), cette approche pourrait ne pas convenir.
Sinon, le gain de performance INP et d'expérience utilisateur l'emporte généralement largement sur la latence minimale de collecte de données.Sinon, le gain de performance INP et d'expérience utilisateur l'emporte généralement largement sur la latence minimale de collecte de données.
Your dev team is busy.
Delegate the performance architecture to a specialist. I handle the optimization track while your team ships the product.
- Parallel Workflows
- Specialized Expertise
- Faster Delivery

