Planifier les événements dataLayer pour optimiser l'INP
Comment différer les événements GTM après le rendu du navigateur améliore vos scores INP

En bref : Améliorer l'INP en différant dataLayer.push()
Lorsque dataLayer.push() se déclenche de manière synchrone à l'intérieur d'un gestionnaire d'événements (event handler), GTM traite les balises avant que le navigateur ne puisse afficher le retour visuel (paint). Cela bloque le thread principal et gonfle votre Interaction to Next Paint (INP). La solution : planifier l'exécution de dataLayer.push() après que le navigateur a affiché la frame suivante en utilisant requestAnimationFrame. Ce modèle réduit l'INP de 20 à 100 ms sur la plupart des sites, transformant souvent un score insuffisant en un score valide. La collecte des données est retardée de 50 à 250 ms, ce qui est sans conséquence pour l'analyse et le suivi marketing.
Comment dataLayer.push() nuit à votre INP
Pour l'un de nos clients, nous avons mesuré une réduction de 100 ms du Interaction to Next Paint (INP) en replanifiant le moment où dataLayer.push() s'exécute après une interaction utilisateur. La solution est un remplacement JavaScript prêt à l'emploi qui donne la priorité au rendu (rendering) sur le suivi (tracking).
Dernière révision par Arjen Karel en mars 2026
Table of Contents!
- En bref : Améliorer l'INP en différant dataLayer.push()
- Comment dataLayer.push() nuit à votre INP
- Le problème avec le dataLayer.push() synchrone
- La solution : afficher d'abord, pousser ensuite
- La fonction d'aide awaitPaint
- Surcharge globale pour des tests rapides
- Pourquoi cela améliore l'INP
- Pourquoi pas un délai fixe, Idle Callback ou scheduler.yield() ?
- Compromis
Le problème avec le dataLayer.push() synchrone
Si vous travaillez avec Google Tag Manager, vous connaissez dataLayer.push(). C'est la méthode standard pour envoyer des événements au Data Layer, et elle est profondément intégrée dans la plupart des fonctionnalités des sites. Le problème est que personne ne s'interroge sur le moment où elle s'exécute.
Lorsque dataLayer.push() est appelé directement à l'intérieur d'un gestionnaire d'événements (un clic sur un bouton, la soumission d'un formulaire, l'ouverture d'un menu), il s'exécute de manière synchrone. Les balises GTM configurées pour se déclencher sur cet événement s'exécutent également immédiatement, bloquant le thread principal avant que le navigateur ne puisse mettre à jour la mise en page. Le visiteur clique sur un bouton et ne voit rien se passer pendant que GTM traite les scripts de suivi en arrière-plan. Ce délai est votre délai de présentation (presentation delay), et c'est le principal facteur d'un mauvais INP.
La trace de performance ci-dessous, provenant d'un grand site d'actualités, montre l'activité de GTM suite à une interaction utilisateur. Les tâches GTM ont mis environ 90 ms à s'exécuter, poussant l'INP global à 263 ms. Cette interaction échoue aux critères des Core Web Vitals.

La solution : afficher d'abord, pousser ensuite
La solution s'aligne sur les conseils d'optimisation de l'INP de web.dev : céder la place au thread principal (yield to main thread) afin que le navigateur puisse effectuer le rendu avant d'exécuter du code non critique. Au lieu d'exécuter le suivi de manière synchrone à l'intérieur du gestionnaire d'événements :
- Exécutez immédiatement le code de la mise à jour visuelle.
- Laissez le navigateur effectuer le rendu (paint).
- Poussez ensuite vers le dataLayer.
Voici la même interaction après l'application de ce modèle. Le seul changement est que dataLayer.push() s'exécute désormais après que le navigateur a affiché la frame suivante.

L'interaction qui échouait auparavant passe maintenant confortablement. Toutes les données parviennent toujours au dataLayer. La différence est que les scripts GTM s'exécutent après que le navigateur a répondu à l'action de l'utilisateur, et non avant.
La fonction d'aide awaitPaint
Cette fonction utilise requestAnimationFrame pour planifier un rappel (callback) après que le navigateur a effectué le rendu. Le setTimeout imbriqué garantit que la fonction s'exécute après la fin du rendu, et non juste avant. C'est le modèle recommandé par web.dev pour différer les travaux non critiques après une interaction.
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Solution de repli : évite le blocage si RAF ne se déclenche jamais (ex: onglets en arrière-plan)
setTimeout(resolve, 200);
// Planification après le prochain rendu
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
Utilisation de la fonction
Enveloppez tout appel à dataLayer.push() dans awaitPaint pour le différer après le rendu :
function pushToDataLayer(event, data) {
window.dataLayer = window.dataLayer || [];
awaitPaint(() => {
window.dataLayer.push({
event,
...data,
timestamp: new Date().toISOString()
});
});
}
// Utilisation
document.querySelector('.buy-button').addEventListener('click', () => {
// Le retour visuel se produit immédiatement
showLoadingSpinner();
// Le suivi se déclenche après le rendu
pushToDataLayer('purchase_click', { productId: '123' });
});
Surcharge globale pour des tests rapides
Pour tester ce modèle sur l'ensemble de votre site sans refactoriser chaque appel dataLayer.push(), vous pouvez surcharger (override) globalement la fonction push. Placez ce script dans le <head> juste après l'extrait du conteneur 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>
Cela surcharge chaque dataLayer.push() sur la page. Tous les déclencheurs d'événements GTM existants, les appels de suivi codés en dur et les intégrations de plugins en bénéficient automatiquement sans aucune modification du code.
Pourquoi cela améliore l'INP
L'INP mesure le temps écoulé entre une interaction utilisateur et le moment où le navigateur affiche la réponse visuelle. Si vous exécutez de manière synchrone le traitement des événements GTM à l'intérieur d'un gestionnaire d'événements, vous bloquez le thread principal et empêchez le rendu. Le Web Almanac 2024 a révélé que le délai de présentation est le principal facteur d'un mauvais INP à la médiane, et les scripts de suivi figurent parmi les causes principales.
Les chiffres le confirment. Le Web Almanac 2025 montre que 77 % des pages mobiles réussissent le test INP dans l'ensemble, mais seulement 63 % des 1 000 sites les plus visités y parviennent. Ces sites de premier plan chargent davantage de scripts tiers. Chez Subito (le plus grand marché de petites annonces en Italie), la désactivation d'un seul script de suivi TikTok chargé via GTM a fait chuter l'INP de 208 ms à environ 170 ms. Un seul script, 38 ms d'économisées.
En différant dataLayer.push() après le rendu, vous retirez tout le traitement GTM du chemin critique de l'interaction. L'utilisateur obtient un retour visuel immédiatement. GTM se déclenche toujours, mais 50 à 250 ms plus tard.
Pourquoi pas un délai fixe, Idle Callback ou scheduler.yield() ?
- Un délai fixe avec
setTimeout(delay): Un délai codé en dur (par exemple,setTimeout(..., 100)) revient à deviner quand le rendu sera terminé. Trop long et vous retardez inutilement le suivi. Trop court et vous bloquez toujours le rendu. requestIdleCallback: Planifie le travail lorsque le navigateur est inactif (idle), mais ne garantit pas une exécution rapide après une interaction spécifique. Le rappel pourrait s'exécuter beaucoup plus tard, ou pas du tout avant que l'utilisateur ne quitte la page.scheduler.yield(): L'API moderne pour céder le contrôle au thread principal. Elle préserve la priorité de la tâche (contrairement àsetTimeoutqui renvoie votre continuation à la fin de la file d'attente). Cependant,scheduler.yield()ne garantit pas qu'un rendu a eu lieu avant l'exécution de votre code. Pour ce cas d'utilisation spécifique,requestAnimationFrameest le meilleur outil car il est lié au cycle de vie du rendu. Notez quescheduler.yield()n'est pas encore pris en charge dans Safari.
La combinaison requestAnimationFrame + setTimeout est spécifiquement conçue pour ce scénario. requestAnimationFrame se déclenche juste avant que le navigateur ne dessine. Le setTimeout imbriqué crée une nouvelle tâche qui s'exécute après la fin de ce rendu. Ensemble, ils garantissent que votre code différé s'exécute après la mise à jour visuelle, ce qui est exactement ce que mesure l'INP.
Compromis
- Micro-délai dans la collecte de données : Les événements atteignent GTM 50 à 250 ms plus tard qu'avec un push synchrone. Pour l'analyse et le suivi marketing, cela est sans conséquence.
- Perte de données en cas de sortie rapide : Si un visiteur déclenche un événement et quitte la page pendant la fenêtre de délai, cet événement peut ne pas se déclencher. Pour les événements critiques avant une redirection ou le déchargement de la page, utilisez
navigator.sendBeacon()au lieu dedataLayer.push(). Consultez les arguments pour limiter les scripts d'analyse pour en savoir plus sur l'API Beacon.
Pour la plupart des suivis, le gain d'INP l'emporte de loin sur le délai minime. Si vous avez besoin d'une précision de l'ordre de la milliseconde dans l'enregistrement des événements (enchères en temps réel, tableaux de bord financiers), cette approche n'est pas adaptée. Pour tout le reste, c'est une victoire évidente.
Après avoir appliqué la surcharge, vérifiez l'amélioration avec le Real User Monitoring. Les tests en laboratoire montreront la différence, mais ce sont les données de terrain provenant d'utilisateurs réels sur des réseaux réels qui comptent pour vos scores Core Web Vitals.
Search Console flagged your site?
When Google flags your Core Web Vitals you need a clear diagnosis fast. I deliver a prioritized fix list within 48 hours.
Request Urgent Audit
