Ajouter des analytics à un site Astro sans bandeau cookies
Analytics sans cookie sur Astro : intégration ou composant, View Transitions, et pourquoi un listener astro:page-load double vos pages vues.
Sommaire
On ne choisit pas Astro par accident. On le choisit pour n’envoyer aucun JavaScript par défaut, pour n’hydrater que les îlots qui en ont besoin, pour servir des pages pré-rendues qui s’affichent avant que le réseau ait fini de respirer. Puis vient le moment de mesurer l’audience, et la première page de résultats propose invariablement le même attelage : une balise publicitaire, un gestionnaire de consentement pour l’encadrer, un bandeau pour demander la permission. Trois scripts tiers et une fenêtre modale posés sur un site conçu pour n’en avoir aucun. Le budget de performance tenu pendant des semaines s’évapore en une balise, et il s’évapore pour obtenir des chiffres partiels, puisque seuls les visiteurs ayant cliqué sur « Accepter » seront comptés.
Le bandeau n’est pourtant pas une étape du travail : c’est la conséquence d’un choix d’outil. Ce guide déroule l’intégration complète côté Astro — l’intégration ou le composant, la navigation client et les View Transitions, les events personnalisés — et s’arrête longuement sur un piège propre au framework : le réflexe le plus naturel qu’Astro vous apprend, appliqué ici, double silencieusement toutes vos pages vues.
Pourquoi le bandeau n’est pas une fatalité
Ce qui déclenche l’obligation de consentement, ce n’est pas le fait de compter des visites. C’est l’écriture ou la lecture d’informations dans le terminal du visiteur : un cookie, une entrée en stockage local, une empreinte de navigateur reconstituée. Une mesure qui n’écrit rien dans le navigateur et ne cherche à reconnaître personne d’une visite à l’autre sort de ce périmètre. Le second point de friction, le transfert de données personnelles vers des serveurs situés hors de l’Union, disparaît de la même façon dès lors que la collecte et le stockage restent en Europe.
Ce qu’il reste est exactement ce dont on a besoin pour piloter un site : des agrégats. Nombre de visites, pages consultées, sources de trafic, répartition par pays et par appareil. Aucun profil individuel, aucun identifiant persistant, personne à reconnaître d’une session à la suivante. C’est le parti pris de conception de Takt : pas de cookie, pas d’identification, un service géré et hébergé en Europe, et un petit runtime navigateur au lieu d’une pile de balises. Le contraste complet avec l’approche Google Analytics est détaillé sur notre page comparative.
Sur un site Astro, la conséquence est plus concrète qu’ailleurs. Un bandeau de consentement est par nature un composant interactif : il faut l’hydrater, lui donner un état persistant, décider quoi charger avant et après le clic. Sur un site majoritairement statique, c’est souvent le seul îlot que vous auriez à embarquer, et il n’apporte rien au visiteur. Sans bandeau, il n’y a ni îlot, ni décalage de mise en page au premier rendu, ni branche « avant/après consentement » à tester. Le gain n’est d’ailleurs pas que technique : personne ne refuse ni n’ignore une fenêtre qui n’existe pas, donc vos chiffres décrivent l’ensemble de votre trafic et non la fraction qui a accepté.
Installer les analytics dans Astro
Avant la ligne de commande, une étape zéro qu’on saute volontiers et qu’on paie ensuite : créez le site dans Takt et notez le domaine que vous y déclarez. C’est cette valeur exacte qui devra figurer dans l’option domain ci-dessous, car c’est le domaine déclaré, et non l’hôte réellement servi, qui rattache les événements à un site. Un déploiement d’aperçu servi sur l’URL générée par votre hébergeur mais déclarant exemple.fr ira donc alimenter les statistiques de la production, et vous passerez un moment à chercher d’où vient ce trafic.
pnpm add @vskstudio/takt-astro @vskstudio/takt-core Deux paquets, parce que @vskstudio/takt-core est une peer dependency de l’intégration, au même titre qu’astro lui-même — d’où la ligne d’installation qui le nomme explicitement plutôt que de s’en remettre à la résolution des peers de votre gestionnaire de paquets. L’intégration est bâtie dessus et injecte un petit runtime navigateur qui démarre Takt, émet le pageview initial et suit la navigation client.
// astro.config.mjs
import { defineConfig } from 'astro/config'
import takt from '@vskstudio/takt-astro'
export default defineConfig({
integrations: [takt({ domain: 'exemple.fr' })]
}) Ce bloc est une configuration minimale, pas un correctif à coller par-dessus la vôtre. Votre projet a déjà un astro.config.mjs, avec des intégrations, sans doute un adaptateur, peut-être une configuration Markdown : ajoutez-y l’import en haut du fichier et l’appel takt(...) dans le tableau integrations existant. Écraser le fichier entier avec ce qui précède vous ferait perdre tout le reste, adaptateur compris.
domain est l’option qu’il faut toujours poser soi-même : son défaut est location.hostname, c’est-à-dire l’hôte qui sert la page, précisément la valeur dont vous ne voulez pas sur un déploiement d’aperçu. Les autres ont toutes un défaut utilisable. spa vaut true, donc la navigation client est suivie ; respectDnt et excludeLocalhost valent true eux aussi, et ces deux-là expliquent la plupart des « je ne vois rien » : on y revient à la fin. Les captures automatiques, elles, sont désactivées par défaut : outbound pour les liens sortants, files pour les téléchargements, track404 pour les pages d’erreur. Vous n’avez pas non plus de directive client: à choisir : le runtime est sûr au SSR et au prérendu, il ne s’exécute que dans le navigateur, ce qui le rend indifférent au mode de sortie de votre projet — entièrement pré-rendu, rendu à la demande, ou les deux dans le même build.
Ou le composant, pour un contrôle par layout
L’autre voie est un composant .astro, à placer dans le <head> d’un layout :
---
// src/layouts/Layout.astro
import Takt from '@vskstudio/takt-astro/Takt.astro'
---
<head>
<Takt domain="exemple.fr" />
</head> Le bloc entre les deux --- est le script du composant, tout en haut du fichier : l’import y rejoint vos imports existants. Le <Takt /> se glisse quant à lui dans le <head> que votre layout possède déjà — le bloc ci-dessus montre un emplacement, ce n’est pas un fragment à ajouter tel quel au bas de votre fichier.
Une règle à ne pas contourner : une seule des deux voies, jamais les deux. Les deux chemins démarrent la même instance par défaut du cœur, et tous deux commencent par poser un drapeau sur window : le second à s’exécuter ne fait donc strictement rien, ni seconde initialisation, ni second pageview, ni second écouteur. C’est ce garde, et lui seul, qui rend la combinaison inoffensive au lieu de doubler votre pageview initial. Vous n’y gagnez pour autant rien, et vous vous retrouvez avec la configuration déclarée à deux endroits, le meilleur moyen de n’en corriger qu’un seul le jour où le domaine change.
Laquelle choisir ? L’intégration pour la quasi-totalité des projets : une ligne dans la configuration, valable pour toutes les pages, y compris celles qu’on ajoutera plus tard sans y penser. Le composant quand la mesure doit dépendre du layout — un site dont une partie seulement est publique, une documentation embarquée qu’on ne veut pas compter avec le reste. Dans ce cas, le composant vit dans le layout mesuré, et l’intégration n’est nulle part.
Le piège : le réflexe astro:page-load
Commençons par ce qui fonctionne sans rien faire, parce que c’est précisément ce qui rend le piège possible. Le routeur client d’Astro effectue plusieurs opérations d’historique par navigation : une restauration de position de défilement via replaceState, puis le pushState de la navigation elle-même. Or le suivi SPA générique du cœur enveloppe ces deux méthodes sans dédoublonner par URL, si bien qu’il compterait la même navigation plusieurs fois. L’intégration Astro le sait et ne s’en sert donc pas : elle désactive ce suivi générique, émet une page vue explicite au démarrage, puis enregistre pour vous un écouteur sur astro:after-swap, l’événement qu’Astro déclenche exactement une fois par navigation du routeur client, boutons « précédent » et « suivant » compris. Une navigation, une page vue, échanges de DOM des View Transitions compris. Vous n’avez donc rien à câbler pour la navigation client, mais retenez par quel moyen : par un écouteur du cycle de vie d’Astro, pas par un enrobage de pushState.
Voici maintenant ce que fait à peu près tout le monde. On ajoute le routeur client, on va lire le guide View Transitions d’Astro, et on y apprend une chose parfaitement exacte : les scripts ne sont pas réexécutés après un échange de DOM, et l’événement astro:page-load est le point d’accroche prévu pour relancer ce qui doit l’être. Le pas suivant paraît évident : brancher un écouteur astro:page-load qui émet une page vue, « pour que la navigation client soit suivie ». Sauf que sur ce site-là, la navigation client était déjà suivie, et suivie exactement de cette manière. L’intégration a déjà branché son propre écouteur sur le cycle de vie d’Astro ; le vôtre vient simplement s’installer à côté du sien. Il n’apporte pas la mesure manquante : il en pose une seconde par-dessus celle qui existait.
Ce qui rend ce piège désagréable, ce n’est pas sa difficulté — c’est qu’il récompense un bon réflexe. La documentation d’Astro vous entraîne à astro:page-load parce que c’est le crochet correct pour tout script devant se remettre au travail après un échange de View Transitions. L’instinct est juste ; il est simplement appliqué à la seule chose du projet qui n’en a pas besoin. Le code passe en revue sans une remarque, il ressemble trait pour trait à la bonne pratique du framework, et il ne lève évidemment aucune erreur.
L’asymétrie mérite d’être énoncée telle quelle, parce que c’est elle qui piège. Avec un outil de mesure qui n’émet une page vue qu’au chargement du document, cet écouteur n’est pas optionnel : c’est lui qui fait toute la mesure des navigations client, et l’omettre laisse un site où seule la page d’entrée est comptée. Ici, il fait double emploi. Une recette d’intégration ne se transpose donc pas d’un outil à l’autre : la question n’est pas « comment suivre les navigations dans Astro », c’est « qu’est-ce que mon outil suit déjà ».
Le doublement est plat, pas croissant : chaque navigation compte deux, pas trois puis quatre. Et il n’épargne pas la première page, puisque astro:page-load se déclenche aussi au chargement initial et pas seulement après une navigation — le visiteur qui arrive et repart aussitôt en aura vu deux. Tout ce qui dérive du nombre de pages vues suit : pages par session doublées, et une visite d’une seule page qui n’en a plus l’air, alors que c’est exactement ce qu’un taux de rebond compte.
Le problème de ces symptômes, c’est qu’ils ne se lisent qu’en agrégat, quelques jours plus tard, et qu’ils ressemblent à une bonne semaine. Il y a beaucoup plus rapide. Ouvrez le site déployé, ouvrez l’onglet Réseau des outils de développement, filtrez sur event, videz le journal, puis cliquez quatre ou cinq liens internes d’affilée et revenez en arrière une fois. Comptez les requêtes vers l’endpoint d’ingestion : il doit y en avoir exactement une par navigation, retour compris. Deux par navigation, et vous tenez le doublon ; l’écouteur est alors le premier endroit à regarder. Le test prend une minute et se refait à chaque déploiement.
Un décompte qui croît — une requête à la première navigation, deux à la deuxième, trois à la troisième — décrit un autre bug : un runtime réinitialisé à chaque navigation, dont l’article Symfony détaille le mécanisme. Le doublon dû à un écouteur reste, lui, obstinément à deux.
La correction est une suppression : retirez l’écouteur, ne le remplacez par rien. Vérifiez au passage que spa n’a pas été basculé à false en cours de route (par exemple pendant que quelqu’un cherchait justement à comprendre le double comptage), car c’est cette option, laissée à son défaut true, qui décide d’enregistrer l’écouteur astro:after-swap. Et gardez astro:page-load pour ce à quoi il sert vraiment : recâbler un écouteur de clic, relancer une carte, réinitialiser un widget après un échange de DOM. Ce n’est pas l’événement qui est en cause, c’est ce qu’on lui a demandé de faire.
Un corollaire mérite d’être connu, parce qu’il tombe exactement de l’autre côté. Puisque l’intégration n’utilise pas le suivi générique de l’historique, une navigation que vous pilotez vous-même sans passer par le routeur client d’Astro (un filtre, un onglet, une pagination poussés dans l’historique à la main) ne déclenche aucun astro:after-swap, et n’émet donc aucune page vue. Rien ne le signale : la requête ne part simplement pas. Si ces vues comptent pour vous, appelez pageview() vous-même au moment où vous changez l’URL. C’est le seul endroit de cette section où ajouter du code est la bonne réponse. Le compromis inverse existe : quand le cœur garde son enrobage de l’historique, tout ce qui écrit une entrée est compté, y compris la navigation superficielle — l’article SvelteKit décrit ce versant-là.
Events personnalisés
Les pages vues ne demandent rien. Pour les conversions — inscription, achat, formulaire envoyé — le cœur réexporte ses fonctions depuis l’intégration :
import { track } from '@vskstudio/takt-astro'
track('Signup', { props: { plan: 'pro' }, revenue: { amount: '9.00', currency: 'USD' } }) Le premier argument est le nom de l’événement, et ce nom est une donnée, pas du texte d’interface : c’est la valeur exacte que vous retrouverez dans le tableau de bord. Gardez-le stable et identique partout. Un Signup traduit en Inscription sur la version française d’un site bilingue ne renomme pas une conversion : il en crée une seconde et coupe la vôtre en deux, sans que rien ne signale la scission. Les propriétés sont des chaînes de caractères, et le montant aussi : '9.00', pas 9. La devise est un code à trois lettres.
Où poser l’appel ? track, comme pageview, optOut et optIn, s’exécute côté navigateur. Il a donc sa place dans un <script> client d’un fichier .astro, ou dans un composant d’un framework UI hydraté — celui que vous avez marqué client:load ou client:visible.
Et c’est ici qu’Astro tend un second piège, plus bénin que le premier mais de la même famille. Le bloc situé entre les deux ---, en haut d’un fichier .astro, n’est pas du code navigateur : il s’exécute au build, ou côté serveur à chaque requête si la page est rendue à la demande. Un track() posé là ne mesure rien chez le visiteur — et comme le runtime ne s’exécute que dans le navigateur, l’appel ne casse rien non plus : il ne se passe simplement rien. Or c’est l’endroit où un développeur Astro écrit l’essentiel de son code. Si un événement personnalisé n’apparaît jamais alors que les pages vues remontent normalement, commencez par vérifier de quel côté de ces trois tirets vit votre appel.
Vérifier que la mesure fonctionne
Premier réflexe à désamorcer : en pnpm dev, vous ne verrez rien remonter, et c’est voulu. excludeLocalhost vaut true par défaut, donc localhost et les IP privées sont ignorés. Cela vaut aussi pour un astro dev --host ouvert depuis votre téléphone sur le réseau local : 192.168.x.x est une adresse privée. Ce n’est pas une configuration ratée, c’est ce qui évite que trois semaines de développement finissent dans les statistiques de production.
Second silence parfaitement légitime : respectDnt vaut true par défaut, donc un navigateur qui envoie Do Not Track n’est pas mesuré. Si vous testez depuis un profil durci, bardé d’extensions de confidentialité, vous êtes peut-être exactement ce visiteur-là. Essayez depuis un autre navigateur avant de conclure à une panne.
La vraie vérification se fait donc en ligne, sur un déploiement ; un déploiement d’aperçu suffit, à condition d’y déclarer le bon domaine. Onglet Réseau, filtre sur event : une requête au chargement de la page, puis exactement une par navigation interne, bouton « retour » compris. C’est le même comptage qu’à la section précédente, et il vous dit deux choses d’un coup : que la mesure part, et qu’elle ne part qu’une fois.
Si la requête part mais que le tableau de bord reste vide, ce n’est plus le navigateur qu’il faut regarder, c’est le domaine déclaré : une valeur qui ne correspond à aucun site de votre compte produit exactement ce tableau — un réseau parfaitement sain d’un côté, aucune donnée de l’autre. Sinon, la page vue apparaît en temps quasi réel dans le tableau de bord, aux côtés des events personnalisés, et vous pouvez passer à autre chose.
En résumé
Deux paquets, un domaine déclaré, une ligne dans astro.config.mjs — ou le composant <Takt /> dans le <head> d’un layout, mais jamais les deux : un site Astro est mesuré sans cookie ni bandeau, View Transitions comprises, et sans rien à câbler pour la navigation client. Le seul point réellement piégeux est le réflexe astro:page-load : Astro vous entraîne à ce crochet parce que c’est le bon pour vos propres scripts, mais l’intégration écoute déjà astro:after-swap pour vous, si bien qu’un écouteur ajouté « pour que la navigation client soit suivie » double chaque page vue, première page incluse, sans lever la moindre erreur ni laisser autre chose qu’une courbe flatteuse. Videz le journal réseau et comptez les requêtes vers l’endpoint d’ingestion sur quelques navigations : une seule par navigation, et il n’y a rien à corriger. La référence complète des options est dans la documentation Astro.
Passer à l'action
Mesurez votre audience sans bannière.
Voyez Takt en action, puis installez une analytics sans cookies sur votre site.
À lire ensuite
Ajouter des analytics sans cookie à une application Symfony
Analytics sans cookie dans une application Symfony 6.4/7.x : fonction Twig, conversions côté serveur, et le piège Turbo qui multiplie les pages vues.
1 août 2026· 9 min de lecture
GuidesAjouter des analytics à une app SvelteKit sans bandeau cookies
Analytics sans cookie dans SvelteKit : composant sûr au SSR dans le layout, events via useTakt, et pourquoi vous ne voyez rien en développement.
1 août 2026· 9 min de lecture
GuidesAjouter des analytics à une app Laravel sans bandeau cookies
Analytics sans cookie dans Laravel 10/11/12 : directive Blade, conversions mesurées côté serveur, et le piège d'attribution des jobs en file.
1 août 2026· 9 min de lecture
Restez au rythme
Un e-mail occasionnel sur la confidentialité, la conformité et les nouveautés Takt. Pas de pistage, pas de spam.
Votre e-mail sert uniquement à l'envoi de la newsletter, jamais revendu.