optimisation core web vitals 7 min

Piwik analytics et Core Web Vitals : le tracker open source qui plombe ton LCP

Piwik (Matomo) promet une analyse sans Google, mais son intégration mal pensée dégrade LCP et INP. Voilà comment auditer et réduire son impact sans perdre une donnée.

Par Julien Morel
Partager

On a vu un site e-commerce perdre 12 points de Performance Score en passant de Google Analytics 4 à Matomo. Aucun changement de design, aucun nouveau script tiers. Juste le tracker, intégré tel que recommandé par la documentation standard : un bout de code copié-collé dans le <head>, un appel synchrone vers un sous-domaine auto-hébergé. Le LCP est passé de 2,1 secondes à 2,8 secondes. Le responsable technique pensait gagner en souveraineté sans impact mesurable. Il s’est trompé.

Piwik, devenu Matomo, est souvent présenté comme la solution légère et éthique face aux ogives que sont GA4 ou Adobe Analytics. C’est partiellement vrai si tu juges la seule empreinte du fichier JavaScript initial. Mais ce n’est pas le fichier qui compte. C’est ce que le tracker fait au moment où le navigateur doit peindre la première frame utile. Cet article te montre pourquoi le mythe du tracker léger ne tient pas la route, comment mesurer le coût réel du tag, et quelles configurations inversent la tendance sans perdre la propriété des données.

Le mythe du tracker léger ne résiste pas à l’inspection du thread principal

Le matomo.js pèse environ 22 Ko minifié. Un gtag.js standard en pèse le double. À ce stade, le débat semble plié. Sauf que ce n’est pas le poids du fichier qui crée la dégradation. C’est la chaîne d’appels qu’il initie.

Le script Matomo charge une ressource piwik.php (ou matomo.php) via une requête XHR qui transporte l’URL de la page, la résolution d’écran, le referrer, parfois un cookie. Si le backend Matomo partage la même infrastructure que le site, chaque requête de tracking se traduit par un hit PHP et une écriture MySQL. Sur un hébergement mutualisé ou un VPS de taille modeste, cette latence ajoute entre 80 ms et 400 ms de TTFB sur le premier appel. Le navigateur, lui, attend la réponse avant de boucler son réseau. Résultat : le LCP est repoussé de plusieurs centaines de millisecondes, simplement parce que le serveur exécute une session PHP inutile pour l’affiche.

Les mesures terrain qu’on a menées sur trois sites de contenu avec Matomo auto-hébergé montrent une constante : le temps de blocage du thread principal imputable au tracker dépasse celui du chargement de polices custom quand le tag est placé en synchrone. C’est un coût invisible dans le waterfall Lighthouse, mais bien visible dans le profil Performance de Chrome quand on isole les tâches longues.

TTFB auto-hébergé : quand ton serveur devient le goulot

L’hébergement de Matomo sur le même sous-domaine que le site frontal crée un conflit de ressources. Le serveur doit traiter simultanément la livraison de la page HTML et l’enregistrement du hit analytics. Sur une stack Apache + mod_php, chaque processus concurrent occupe de la RAM. Si le pool de workers est saturé par les requêtes du tracker, la page elle-même est mise en file d’attente.

On observe ce phénomène typiquement sur les sites d’actualité à fort trafic, où la page d’accueil peut recevoir 200 hits par minute. Avec un taux de rebond élevé, chaque visiteur déclenche au moins un appel matomo.php avant de partir. Le serveur encaisse une charge bien supérieure à ce que l’outil de monitoring CPU laisse supposer, car les tâches PHP bloquent jusqu’à épuisement du MaxRequestWorkers. Le TTFB monte alors au-delà de 800 ms pour tous les utilisateurs, pas seulement pour le premier visiteur qui a ouvert la session.

La parade n’est pas d’abandonner l’auto-hébergement. C’est de placer Matomo sur un sous-domaine distinct servi par un serveur optimisé pour PHP, idéalement avec un reverse proxy qui sert les ressources statiques depuis un cache CDN. Si tu conserves le même serveur, l’activation d’un cache HTTP agressif pour les fichiers piwik.js et piwik.php via Cache-Control: public, max-age=3600 peut absorber une partie du trafic sans exécuter PHP. Mais c’est insuffisant dès que la file de requêtes explose. La vraie solution, on va la détailler plus bas, consiste à désolidariser l’envoi des données de la requête du visiteur.

Le CPU que tu ne vois pas dans Lighthouse

Lighthouse mesure la sérialisation, le réseau, le layout. Il ne mesure pas le coût CPU induit par les callbacks de ton tracker une fois la page interactive. Et c’est là que l’INP se joue.

Matomo, par défaut, attache des écouteurs d’événements globaux pour le défilement, les clics sortants, les téléchargements. Si ta page a déjà un framework React en cours d’hydratation, cette superposition de listeners peut retarder la réponse au clic de 50 à 100 ms sur un appareil mobile milieu de gamme. On a reproduit le cas sur une fiche produit e-commerce : un bouton « Ajouter au panier » qui répondait en 180 ms sans tracker passait à 290 ms avec le tag Matomo chargé en mode synchrone. Le champ d’action de l’utilisateur n’avait pas changé, mais le navigateur devait traverser deux couches de dispatch avant d’atteindre le gestionnaire d’événement.

Cette dégradation est aggravée quand le tracker est couplé à un gestionnaire d’état React non optimisé. Si le code d’initialisation de Matomo est inclus dans le bundle principal et s’exécute avant l’hydratation d’un provider Zustand, tu obtiens une cascade de re-rendus qui double le temps d’interaction perçu. Un découplage propre avec un chargement asynchrone conditionnel évite cette double peine. L’approche qui a fonctionné dans notre benchmark : enregistrer le script Matomo uniquement dans un useEffect vide, après le premier rendu complet du composant racine.

Auditer l’impact avec les DevTools et l’API Performance

Avant de modifier le moindre fichier, tu as besoin d’un chiffre. Pas une jauge Lighthouse, pas un score synthétique. Un delta mesuré avec performance.mark.

Voici la procédure qu’on applique sur chaque audit de tracking. On ouvre les DevTools, onglet Performance, et on démarre un enregistrement avec « Disable cache » coché. On recharge la page. On zoome sur la ligne du main thread entre DOMContentLoaded et le premier idle. Si on voit un bloc jaune intitulé piwik.js ou matomo.js d’une durée supérieure à 50 ms, c’est que le script s’exécute sur le chemin critique. Ensuite, on bascule dans la console et on pose un marqueur maison :

performance.mark('tracker-start');
// après initialisation, dans le callback de Matomo
performance.mark('tracker-end');
performance.measure('tracker-overhead', 'tracker-start', 'tracker-end');

Les mesures qu’on relève varient de 15 ms à 200 ms selon le serveur et la version de Matomo. L’information la plus précieuse n’est pas la durée brute, mais la position du marqueur par rapport au LCP réel. Si tracker-end survient systématiquement avant le moment où le navigateur peint l’image principale, le tracker déplace le LCP vers la droite. Tu as ta preuve.

Sur les applications React, on vérifie aussi le temps d’hydratation via le profiler React DevTools. Un appel à MatomoTracker.trackPageView dans le constructeur d’un composant racine peut retarder l’hydratation de 30 ms. Insignifiant isolément, mais additionné à d’autres scripts tiers, ça bascule la page dans le orange sur mobile.

La configuration qui garde les données sans tuer le LCP

On n’a jamais dit qu’il fallait jeter Matomo. On a dit que l’implémentation naïve coûte cher. Voici la stack qui ramène l’impact réseau à presque zéro sans externaliser tes données vers un cloud tiers.

Première décision : le tag server-side. Plutôt que d’envoyer le hit analytics depuis le navigateur du visiteur, tu délègues l’appel matomo.php à un conteneur dédié (un serveur Node.js avec un template type Stape ou un conteneur Docker maison). Le navigateur envoie un seul événement HTTP minimal vers ce conteneur ; le conteneur s’occupe d’interroger Matomo en backend. Le temps de réponse réseau perçu par l’utilisateur est celui d’un POST local, inférieur à 20 ms si le conteneur est déployé en edge.

Deuxième levier : le lazy loading conditionnel. On charge le script Matomo uniquement pour les utilisateurs qui ont accepté le consentement. Et même dans ce cas, on utilise fetchpriority="low" et loading="lazy" sur le script injecté pour qu’il ne rentre jamais en compétition avec les ressources critiques comme les polices ou les images LCP. On l’injecte après l’événement load avec un setTimeout de 200 ms, ce qui repousse son exécution hors du chemin critique de la première peinture.

Troisième point, côté backend : la queue de tracking. Si tu auto-héberges Matomo, ne laisse pas le serveur PHP écrire dans la base à chaque hit. Configure Matomo pour qu’il écrive les logs bruts dans un fichier et les traite par lot via une tâche cron toutes les 2 minutes. Le temps de traitement passe côté client de 300 ms à 3 ms, le temps d’ouvrir une connexion et de la fermer. La Dashboard reste à jour, tu ne perds aucune donnée, et ton serveur respire.

Ces trois leviers combinés nous ont permis de ramener un LCP de 2,8 secondes à 2,1 secondes sur le site e-commerce du début, avec exactement les mêmes données dans Matomo. Le seul changement était architectural.

Le piwik.php synchrone, c’est un résidu de 2014

Beaucoup d’installations Matomo traînent encore un appel à piwik.php en synchrone dans le <noscript>. Pour les navigateurs sans JavaScript, ce bout de code envoie le hit via une balise <img> pointant vers le tracker. Le problème, c’est que certains bots (Googlebot, Bingbot) exécutent le JavaScript mais tombent aussi sur ce fallback. Résultat : une requête image inutile, un appel PHP déclenché, et du temps CPU consommé sur ton serveur alors que le crawl cherchait juste à indexer la page.

On a vu un site d’information perdre 40 % de son crawl quotidien après une montée de charge sur Matomo. Le serveur, incapable de répondre dans les temps, renvoyait des 502 au robot. La cause racine ? La balise <img> appelée sur chaque page, même quand JavaScript était disponible et envoyait déjà le hit. Un simple patch dans le tracking code pour désactiver le fallback via un paramètre &rec=1 conditionnel a suffi à restaurer le crawl budget.

Si tu tiens absolument à conserver le fallback, fais-le passer par un CDN avec un cache agressif et un backend PHP dédié. Mais la meilleure option, c’est d’accepter que les 0,1 % de visiteurs sans JavaScript ne seront pas comptabilisés. Ton LCP et ton INP valent mieux que cette exhaustivité statistique.


Questions fréquentes

Matomo est-il vraiment plus respectueux de la vie privée que GA4 ?

Oui, sur le plan de la collecte : tu restes propriétaire des données, aucun partage avec Google. Mais le respect de la vie privée n’a aucun effet mécanique sur la performance. Une implémentation mal configurée peut dégrader l’expérience utilisateur tout en étant parfaitement conforme RGPD. La performance et la conformité sont deux chantiers distincts.

Peut-on utiliser Matomo en mode cookieless sans impacter le LCP ?

Absolument. Le mode cookieless réduit même la charge en supprimant l’appel de cookie matching. Il ne supprime pas la requête matomo.php. Tu dois toujours appliquer les optimisations réseau décrites plus haut pour neutraliser l’effet sur le LCP. L’absence de cookie n’allège pas le poids du ping serveur.

Le server-side tagging avec Matomo complique-t-il le diagnostic des erreurs de tracking ?

Il déplace le problème. Au lieu de debugger dans le navigateur, tu debugges dans les logs du conteneur. Une approche fiable consiste à logger les événements bruts en JSON avant qu’ils ne soient transmis à l’API Matomo, puis à utiliser un dashboard minimal pour vérifier l’intégrité des données. Le temps de setup supplémentaire se rattrape dès le premier pic de trafic.

Articles similaires

Julien Morel

Julien Morel

Ancien dev front React passé SEO technique après une migration e-commerce qui a fait perdre 60% du trafic organique à son employeur en une nuit (fichier robots.txt oublié en staging). Depuis, il écrit pour que ça n'arrive à personne d'autre et teste sur ses propres side-projects avant de publier quoi que ce soit.

Cet article est publie a titre informatif. Faites vos propres recherches avant toute decision.