Si vous suivez une application React, Vue, Angular ou Next.js avec un outil analytics et que vos rapports affichent une seule page vue par session, vous avez un problème de tracking SPA. C'est probablement le bug le plus courant — et le plus invisible — des implémentations analytics modernes.
Le diagnostic est simple : votre site change de route sans recharger la page, et votre outil analytics ne sait pas que l'utilisateur a navigué. Résultat : un funnel cassé, des taux de rebond artificiellement élevés et des décisions marketing prises sur des données fausses.
Trois méthodes permettent de corriger ça. Le principe est le même quel que soit votre outil — GA4, Piano Analytics, Matomo, Piwik PRO, Adobe Analytics, Plausible — mais chacune a un cas d'usage, des limites et un niveau de contrôle différent. Cet article les compare, avec les pièges que je rencontre régulièrement en mission de consulting tracking.
Pourquoi les outils analytics perdent la trace dans une SPA
Une Single Page Application charge une fois la page initiale, puis met à jour le contenu via JavaScript sans appel serveur. Les frameworks modernes utilisent l'History API du navigateur (pushState, replaceState) pour changer l'URL sans déclencher de rechargement.
Le tracking analytics traditionnel, lui, repose sur l'événement page_load du navigateur pour envoyer une vue de page. Sur une SPA, cet événement ne se produit qu'une seule fois : à l'arrivée de l'utilisateur. Toutes les navigations internes deviennent invisibles, quel que soit l'outil de mesure utilisé.
Méthode 1 : la détection automatique fournie par l'outil
Activer la détection SPA native de votre outil analytics
Méthode 1La plupart des outils analytics modernes embarquent une fonctionnalité de détection automatique des changements d'URL :
- GA4 — la mesure améliorée ("Changements de page basés sur les événements d'historique")
- Piano Analytics — la propriété
spa: truedans la configuration du SDKpa.js, qui active la détection despushState - Matomo — le plugin "SPA tracker" ou
trackPageViewcouplé au heartbeat - Piwik PRO — l'option Track virtual page views activée dans le conteneur, ou
_paq.push(['trackPageView'])au changement de route - Adobe Analytics — la fonction
track()avec l'option Single Page App - Plausible — l'option
manualdésactivée par défaut, avec auto-tracking viascript.tagged-events.js
page_title qui renvoient la valeur de la page précédente, des doublons sur les navigations avec redirection (replaceState après login), et des vues parasites sur les changements d'URL purement techniques (modaux, filtres).
Méthode 2 : le déclencheur "History Change" du TMS
Déléguer la détection au Tag Management System
Méthode 2Cette méthode reprend la main sur l'outil analytics en déplaçant la logique dans un Tag Management System (Google Tag Manager, Tealium, Commanders Act, Piano TMS…). C'est l'approche la plus répandue en implémentation professionnelle parce qu'elle équilibre simplicité et contrôle.
Principe de configuration, illustré ici avec GTM mais identique dans les autres TMS :
- Créer un déclencheur de type History Change. Il se déclenche sur les événements
pushState,replaceStateetpopstate. - Créer une balise "vue de page" pour votre outil analytics en y mappant
Page URL,Page Titleet les paramètres souhaités. - Associer la balise au déclencheur.
Méthode 3 : pousser un événement custom dans le dataLayer
Émettre l'événement depuis le code applicatif
Méthode 3C'est la méthode la plus robuste et celle que je recommande pour les sites e-commerce, les applications avec funnel complexe, ou tout projet où la qualité des données est critique. Elle fonctionne avec n'importe quel outil analytics consommant un dataLayer (ou son équivalent).
Le principe : c'est le code de l'application qui décide quand notifier le tracking d'un changement de page. Vous ajoutez une instruction dans le router de votre framework qui pousse un événement après que le contenu et le titre ont été mis à jour.
React Routerimport { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export const usePageViewTracking = () => {
const location = useLocation();
useEffect(() => {
requestAnimationFrame(() => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'virtual_page_view',
page_path: location.pathname + location.search,
page_title: document.title,
page_location: window.location.href,
});
});
}, [location]);
};
Vue Router
router.afterEach((to) => {
setTimeout(() => {
window.dataLayer.push({
event: 'virtual_page_view',
page_path: to.fullPath,
page_title: document.title,
page_location: window.location.href,
});
}, 0);
});
Côté TMS, vous créez ensuite un déclencheur sur l'événement personnalisé virtual_page_view qui pilote la balise vue de page de votre outil (peu importe lequel).
Comparatif des 3 méthodes
| Critère | M1 — Détection auto | M2 — History Change TMS | M3 — DataLayer custom |
|---|---|---|---|
| Effort de mise en place | Aucun | Faible | Moyen |
| Contrôle du timing | Très faible | Moyen | Total |
| Risque de titre incorrect | Élevé | Moyen | Faible |
| Risque de doublons | Faible si seul | Faible si M1 désactivée | Faible |
| Paramètres custom | ✗ | ✓ | ✓ illimités |
| Maintenance long terme | Aucune | Faible | À documenter côté code |
| Cas type | Vitrine, blog | 70 % des SPA standard | E-commerce, SaaS, data-critique |
Les pièges récurrents
Au-delà du choix de la méthode, certains problèmes reviennent dans presque toutes les implémentations SPA.
requestAnimationFrame ou poussez l'événement dans le hook afterEach du router.
pushState et donc une vue parasite. Soit vous filtrez côté TMS, soit vous passez en méthode 3.
/login, est redirigé en replaceState vers /dashboard. La méthode 1 envoie deux vues, dont une sur /login jamais vraiment vue. Préférez la méthode 3 sur ces parcours.
Comment valider votre tracking SPA
Aucune des méthodes ne se déploie sans validation. Le minimum avant production : mode prévisualisation du TMS, mode debug de l'outil analytics (DebugView GA4, Real-Time Matomo, console Adobe…), comparaison du nombre de vues envoyées vs attendues sur un échantillon de 10 navigations réelles. Si vous obtenez 9 ou 11 vues au lieu de 10, le tracking est cassé.
Testez aussi les cas limites : bouton retour navigateur, ouverture en nouvel onglet, refresh sur une page interne, deep link partagé. Tous ces parcours doivent émettre exactement une vue.
Quelle méthode pour quel projet
Trois questions suffisent à trancher.
Si vos décisions business reposent sur les rapports analytics (allocation média, attribution, A/B testing), le coût de mise en place est négligeable face au coût d'une décision prise sur des données fausses.
Si vous avez besoin de catégoriser, segmenter ou enrichir la vue de page avec des paramètres applicatifs (ID utilisateur, étape funnel…), la méthode 1 est éliminée.
Sur une stack mature avec routage standard, le déclencheur History Change du TMS fonctionne bien et reste maintenable sans intervention dev.
Sur Next.js App Router, Remix, ou des architectures avec micro-frontends, le push dataLayer reste la seule méthode fiable.
Conclusion
Tracker une Single Page Application n'est pas un problème technique difficile. C'est un problème de choix d'architecture, et il se pose de la même façon quel que soit l'outil analytics utilisé. La détection automatique répond à 80 % des cas avec un effort nul, le déclencheur History Change du TMS à 95 % avec un peu de configuration, le push dataLayer custom à 100 % avec un peu de code.
Le piège classique : empiler les méthodes au lieu d'en choisir une. Si vous prenez une seule décision après cet article, vérifiez que vous n'avez pas la détection automatique de votre outil activée en même temps qu'une balise TMS de vue de page. C'est le bug le plus fréquent et le plus simple à corriger.
À lire aussi
→Votre tracking SPA envoie-t-il les bonnes données ?
Audit complet de votre implémentation analytics (GA4, Piano, Matomo, Piwik PRO…) sur SPA, identification des doublons, recommandations priorisées. Échangeons 30 minutes sur votre setup.
Prendre RDV →