Quand on a déplacé un site e-commerce de 3000 pages de GA4 vers Matomo, le LCP médian mobile est passé de 3,2 s à 2,7 s. Pas parce que le tracker pèse moins lourd. Parce qu’on a pu reprendre la main sur l’ordre d’exécution des scripts. Et ça, aucun tag management externe ne te le donne aussi franchement.
L’analytics auto-hébergé, on le vend souvent comme un choix de souveraineté ou de conformité RGPD. C’est vrai, mais c’est regarder par le petit bout de la lorgnette. Ce qui se joue, c’est la capacité à traiter le tracking comme une ressource interne : même arbre de dépendances, mêmes priorités de chargement, mêmes contraintes de performance que ton propre JavaScript métier. Si tu continues à le brancher comme un script tiers opaque, tu n’as rien gagné.
La question qu’on aurait dû se poser avant de migrer GA4
La migration analytics est rarement traitée comme un sujet de performance web. La fiche de comparaison entre GA4 et Matomo que se refilent les équipes mentionne le sampling, la propriété des données, le coût de la version cloud. On y trouve rarement les deux lignes qui comptent pour un lead dev front : poids du script principal et mode de chargement par défaut.
Avec GA4, la librairie gtag impose un chargement asynchrone qui insère une balise <script> dynamique dans le <head>. Le navigateur découvre cette ressource tard, la télécharge en parallèle, et exécute immédiatement les callbacks de configuration. Résultat : un TBT qui peut grimper de 150 ms sans que personne ne le voie dans les dashboards, parce que le thread principal est pris par l’envoi de hits au moment où ton hydratation React doit se terminer. Matomo, lui, tu le places en <script defer src="/js/piwik.js"> tout en bas de ta page si tu veux, et le tracker ne s’exécute qu’une fois le DOM complet. La collecte ne perd rien, le LCP ne subit pas.
Le poids d’un script analytics, ça se juge au TBT, pas au kilooctet
On fait encore trop souvent l’erreur de regarder les kilooctets d’un script de tracking comme si c’était un indicateur de performance. Un fichier de 40 ko compressé, ça ne dit rien du temps de parsing, du nombre d’appels réseau synchrones déclenchés à l’initialisation, ni des interactions avec le gestionnaire de consentement. Une install Matomo basique sur un vhost dédié, avec l’option disableCookies et sans gestionnaire de tags, injecte un seul fichier JavaScript et envoie une requête POST à /piwik.php. Le TBT ajouté est quasi nul si le script est deferée.
Le vrai casse-tête, c’est la multiplication des greffons. Heatmaps, session recordings, A/B testing intégré, rapports en temps réel qui ouvrent un websocket. Chaque module alourdit le temps de blocage du thread principal parce que le tracker Matomo global les charge synchroniquement par défaut si tu actives les fonctionnalités depuis l’interface d’administration. On a mesuré sur un side-project Next.js : activer les heatmaps sans toucher au code ajoutait 80 ms de TBT sur mobile. Désactiver le module et n’utiliser que la mesure de page vue redonne le contrôle. La règle est la même qu’avec un bundle applicatif : tu embarqués seulement ce dont tu as besoin côté client.
Priorité de chargement : la variable que Matomo te laisse régler
Le mécanisme est trivial, mais il est absent de l’écosystème GA4 : avec Matomo, le script de tracking est un fichier comme un autre sur ton serveur. Tu peux l’intégrer dans ton pipeline de build, le versionner, et surtout décider du hint de priorité.
Sur notre projet e-commerce, on a aligné le chargement du tracker avec les guidelines qu’on applique déjà à nos bundles applicatifs : <script defer fetchpriority="low"> sur le script Matomo, et une preconnect uniquement vers le domaine d’analytics quand l’utilisateur a donné son consentement. Résultat : le navigateur ne voit plus le tracker comme une ressource critique pour le premier rendu. Le LCP n’est plus influencé par les allers-retours DNS et TCP d’un domaine tiers. Tu ne peux pas reproduire ça avec gtag, car le script se charge depuis un CDN sur lequel tu n’as pas la main et la librairie force son propre timing d’initialisation.
💡 Conseil : Si vous avez plusieurs sous-domaines, réutilisez la même instance Matomo via une
preconnectunique plutôt que de multiplier les domaines de tracking. Chaque nouveau domaine ajoute une résolution DNS qui retarde le chargement des ressources utiles au visiteur.
Ce qu’un defer sur le script Matomo change concrètement sur une fiche produit Next.js
On a instrumenté une fiche produit avec Lighthouse sur un mobile ralenti 4x, en comparant trois modes : script Matomo injecté dans le <head> (mode historique), script defer dans le <body>, et script chargé dynamiquement après l’événement load via un écouteur.
La version <head> affichait un LCP à 3,1 s, avec un TBT de 190 ms. La version defer tombait à 2,5 s de LCP et 90 ms de TBT. La version chargée après load faisait mieux sur le TBT (30 ms) mais perdait en complétude des données car la première page vue pouvait être ratée si l’utilisateur naviguait vite. Le defer est le point d’équilibre : tu ne dégrades pas la qualité de la collecte, et tu libères le thread principal pour l’hydratation et l’interactivité.
Ce test, on l’a reproduit avec l’App Router de Next.js 14, en utilisant le composant Script avec strategy="lazyOnload" pour le tracker. Si tu gères l’injection du tracking via un état global dans Zustand, vérifie que l’appel à _paq.push() ne se ré-exécute pas à chaque changement de store. Une ré-hydratation mal maîtrisée du wrapper analytics peut produire deux fois plus d’événements et remonter le TBT de 40 ms sans que tu le suspectes.
L’envers du self-hosted quand ton serveur d’analytics ralentit le crawl
Héberger Matomo sur le même serveur que le site frontal crée un couplage qu’on ne voit jamais dans les comparatifs. Si un crawl intensif de Googlebot déclenche des appels au tracking, le backend d’analytics peut saturer IO et allonger le TTFB pour les utilisateurs. On le sait parce qu’on l’a subi chez un client. Le log serveur a montré des vagues de POST /piwik.php juste avant les pics de TTFB enregistrés par la Search Console.
Pour éviter ça, on sépare les plans. Matomo tourne sur un sous-domaine dédié, avec son propre cache Redis, et on conditionne l’appel tracking côté client à une vérification de navigator.webdriver pour ne pas injecter le script lorsque du trafic automatisé évident se présente. Le gain n’est pas dans Matomo, il est dans l’architecture. C’est le même raisonnement que pour l’optimisation des signaux Web Vitals en général : chaque ressource qui n’est pas essentielle au premier rendu doit être isolée pour ne pas contaminer les métriques critiques.
Pourquoi le rapport de vitesse intégré de Matomo n’est pas un indicateur de pilotage
Matomo embarque une fonction de mesure du temps de chargement des pages, transmise via l’API _paq.push(['setGenerationTimeMs', time]). On a beaucoup utilisé cette métrique en interne avant de se rendre compte qu’elle mesure le temps écoulé côté serveur jusqu’à l’envoi du hit, pas le LCP réel. Sur un site dont le rendu est majoritairement côté client, le chiffre Matomo peut annoncer 200 ms là où Lighthouse affiche 2,5 s.
C’est un leurre confortable. La seule métrique qui corrèle avec le ressenti utilisateur et les signaux de classement Google, c’est le LCP mesuré par l’API PerformanceObserver ou le rapport CrUX. Garde Matomo pour les données d’audience, et branche le reporting performance directement dans le navigateur. Sur le même side-project, on a un middleware qui injecte un script type="module" léger chargé de remonter les largest-contentful-paint dans les logs serveur, sans passer par l’infrastructure analytics. C’est plus fiable et ça évite de gonfler le tracker avec des mesures qui n’ont pas besoin de consentement.
⚠️ Attention : Si vous activez le plugin
HeatmapSessionRecordingde Matomo, le poids total des scripts injectés côté client peut dépasser les 200 Ko. Sur une page au DOM lourd, le temps de sérialisation des nœuds pour l’enregistrement dégrade le INP bien avant qu’un témoin d’alerte ne se déclenche.
Questions fréquentes
Est-ce que Matomo est toujours plus léger que GA4 ?
Pas intrinsèquement. Le script de base de Matomo pèse environ 22 Ko minifié, contre 45 Ko pour la bibliothèque gtag standard. Mais le vrai écart se fait sur le nombre de requêtes réseau initiales : Matomo peut fonctionner avec une seule requête POST par page vue, là où GA4 enchaîne plusieurs appels à des sous-domaines distincts si la mesure avancée est activée. Le bénéfice se joue sur le plan de taggage, pas sur les seuls kilooctets.
Pourquoi ne pas utiliser un autre outil auto-hébergé comme Plausible ?
Plausible est plus léger, avec un script sous les 1 Ko, mais il est volontairement limité en fonctionnalités. Si vous avez besoin d’entonnoirs de conversion, d’objectifs personnalisés ou d’un suivi e-commerce enrichi, Matomo reste le seul outil auto-hébergé qui rivalise avec la couverture fonctionnelle de GA4 tout en te laissant le contrôle de la couche de chargement. Et pour débuguer l’impact de ces deux solutions, les IDE comme Claude Code ou Cursor permettent de poser des console.time directement dans le composant qui injecte le tracker.
Le tracking Matomo peut-il être bloqué par les bloqueurs de publicité ?
Oui, certains bloqueurs incluent le domaine matomo.js dans leurs listes, mais comme tu maîtrises le nom de fichier et le chemin, tu peux renommer le script de tracking en analytics.js et router la requête via une règle de réécriture interne. C’est une pratique grise, discutable en termes de transparence, mais techniquement valide. La meilleure approche reste de documenter clairement la collecte dans ta politique de confidentialité : le consentement explicite rend le blocage agressif moins probable.