takt

Astro

Astro a une intégration dédiée : @vskstudio/takt-astro, bâtie sur le cœur @vskstudio/takt-core. Elle injecte un petit runtime navigateur qui boote Takt, émet le pageview initial et suit la navigation client — y compris les View Transitions d'Astro.

pnpm add @vskstudio/takt-astro @vskstudio/takt-core

@vskstudio/takt-core (>=0.10.0 depuis la 0.8.0) et astro (>=4) sont des peer dependencies. Le runtime est sûr au SSR / prerender : il est gardé par un test typeof window et ne s’exécute que dans le navigateur.

Cette page est la référence des options. Pour l’intégration de bout en bout et son piège principal — l’écouteur astro:page-load ajouté en trop, qui double chaque page vue — voir le guide analytics sans cookie sur un site Astro.

Choisis une seule des deux voies ci-dessous — pas les deux. Les deux bootent l'instance par défaut du cœur derrière le même drapeau window.__takt : la seconde voie chargée ne réinitialise rien, elle ne fait strictement rien. Tu n'aurais donc pas de double comptage, juste un second script embarqué pour rien.

Intégration (recommandé)

Ajoute takt() à la liste integrations de ta config Astro :

// astro.config.mjs
import { defineConfig } from 'astro/config'
import takt from '@vskstudio/takt-astro'

export default defineConfig({
  integrations: [takt({ domain: 'exemple.fr' })]
})

Composant <Takt />

Pour un contrôle par layout, place le composant dans le <head> d’un layout :

---
import Takt from '@vskstudio/takt-astro/Takt.astro'
---
<head>
  <Takt domain="exemple.fr" />
</head>

L’intégration et le composant acceptent les mêmes options, à une exception près : scrubUrl est une fonction et n’est acceptée que par l’intégration (voir la note sous le tableau).

OptionTypeDéfautRôle
domainstringlocation.hostnameIdentifiant du site
endpointstringhttps://taktlytics.com/api/eventURL d’ingestion ; mets /api/event pour un proxy first-party same-origin
scriptOriginstring—Origine dont dériver l’endpoint ({origin}/api/event), pour un domaine custom proxifié ; endpoint reste prioritaire
outboundbooleanfalseSuit les liens sortants
filesbooleanfalseSuit les téléchargements
spabooleantrueSuit la navigation client (mappé sur l’option auto du cœur)
track404booleanfalseSignale un événement 404 sur les pages d’erreur (marqueur [data-takt-404] / <meta name="takt:404">, ou statut HTTP 404).
taggedbooleanfalseSuit les éléments porteurs de data-takt-event
respectDntbooleantrueRespecte Do Not Track
excludeLocalhostbooleantrueIgnore localhost / IP privées
enabledbooleantrueInterrupteur général : à false, plus aucun event
sampleRatenumber1Fraction de visiteurs suivis, de 0 à 1
trackQuerybooleanfalseEnvoie l’URL complète au lieu d’en retirer query et hash
queryParamsstring[]—Liste blanche de paramètres conservés quand trackQuery est off
excludestring[]—Préfixes de chemin jamais suivis, ex. ['/app','/compte'] (bornés au segment : /app couvre /app et /app/…, pas /application)
scrubUrl(url: string) => string—Intégration seulement. Réécrit chaque URL avant envoi (page, referrer, url des liens sortants et des téléchargements, path des 404)
debugbooleanfalseTrace chaque payload dans la console du navigateur avant l’envoi
redactRoutesstring[]aucunMotifs de routes envoyés à la place du chemin réel, ex. ['/verify/[token]'] envoie /verify/[token] au lieu de /verify/abc123 (voir Masquage de routes)
routeTemplatesbooleanfalseEnvoie chaque page sous la forme de son gabarit de route Astro (/blog/[slug]), lu depuis Astro.routePattern. Exige Astro 5 et la balise de route
scrubUrl est une fonction : elle est sérialisée à la compilation via toString() puis réévaluée dans le navigateur. Elle doit donc être autonome — aucune variable de closure ni référence au scope englobant — et ne jamais être construite à partir d'entrées utilisateur. Le composant <Takt />, lui, sérialise sa config en îlot de données JSON, qui ne peut pas transporter de fonction : lui passer scrubUrl lève une erreur à la compilation plutôt que de l'ignorer en silence.

Masquage de routes

La query string est retirée par défaut, mais le chemin part en clair : /verify/abc123 transmet le jeton. Liste les routes sensibles dans redactRoutes : chaque chemin qui correspond part sous la forme de son motif, les autres pages gardent leur chemin réel.

takt({ redactRoutes: ['/verify/[token]', '/invoices/[id].pdf'] })

La règle couvre l’URL de la page, le referrer du même site, les liens sortants et les téléchargements, et le chemin des 404. Les motifs suivent la syntaxe d’Astro ([param], [...reste]) et acceptent aussi :param et *.

Pour envoyer plutôt chaque page sous la forme de son gabarit de route (/blog/bonjour devient /blog/[slug]), active routeTemplates. Astro connaît le gabarit côté serveur (Astro.routePattern) : il voyage dans une balise <meta name="takt:route"> que le runtime relit à chaque pageview, y compris après une navigation ClientRouter. Avec l’intégration, rends cette balise avec <TaktRoute /> dans le <head> de ton layout :

// astro.config.mjs
export default defineConfig({
  integrations: [takt({ routeTemplates: true })]
})
---
import TaktRoute from '@vskstudio/takt-astro/TaktRoute.astro'
---
<head>
  <TaktRoute />
</head>

Le composant <Takt /> rend la balise lui-même quand routeTemplates est actif :

<Takt domain="exemple.fr" routeTemplates />

Astro.routePattern existe depuis Astro 5. Sous Astro 4, aucune balise n’est rendue : redactRoutes s’applique quand même, sinon le chemin réel part. Ce mode fusionne tous les articles d’un blog public en une seule ligne, il convient donc mieux aux espaces privés ; sur un site public, redactRoutes est en général le bon outil. Détails dans Vie privée.

View Transitions

Le ClientRouter d’Astro effectue plusieurs opérations d’historique par navigation (un replaceState de restauration de scroll, puis un pushState). Le patch d’historique du cœur compterait donc en double. L’intégration l’écarte : elle initialise le cœur avec auto: false, ce qui désactive à la fois le patch pushState/replaceState et le pageview de boot.

Le runtime pilote donc les pageviews depuis le cycle de vie d’Astro :

  • un pageview initial explicite au boot du script — ce qui couvre aussi les sites MPA classiques, où le script est réexécuté à chaque chargement de page et où astro:after-swap ne se déclenche jamais ;
  • puis un pageview par événement astro:after-swap, émis exactement une fois par navigation du ClientRouter (échanges de DOM des View Transitions et retour/avant compris), quand spa est actif.

Chaque navigation est ainsi comptée exactement une fois.

N'ajoute pas de listener astro:page-load pour suivre les pages : le runtime enregistre déjà son propre astro:after-swap. Le tien en installerait un second et compterait chaque navigation deux fois.
Corollaire : puisque le patch d'historique du cœur est désactivé, un history.pushState que tu pilotes à la main — filtres, onglets, pagination hors ClientRouter — ne déclenche aucun astro:after-swap, donc aucun pageview. C'est le seul cas où appeler pageview() toi-même est la bonne réponse.

Events personnalisés

Les fonctions du cœur sont réexportées par commodité :

import { track } from '@vskstudio/takt-astro'

track('Signup', { revenue: { amount: '9.00', currency: 'USD' } })

track/pageview/optOut/optIn/isOptedOut s’exécutent côté navigateur : appelle-les depuis un <script> client ou un composant d’un framework UI hydraté.

Consentement

optOut, optIn et isOptedOut n’attendent pas le démarrage du runtime : ils lisent et écrivent directement le choix stocké. Une bannière de consentement peut donc s’exécuter avant le runtime injecté, et l’instance qu’il démarre ensuite respecte le choix. Appelle-les depuis un <script> client, puisque le choix vit dans le navigateur.

<button id="analytics-toggle"></button>
<script>
  import { isOptedOut, optIn, optOut } from '@vskstudio/takt-astro'

  const button = document.getElementById('analytics-toggle')
  const render = () => {
    if (button) button.textContent = isOptedOut() ? 'Activer la mesure' : 'Désactiver la mesure'
  }
  button?.addEventListener('click', () => {
    if (isOptedOut()) optIn()
    else optOut()
    render()
  })
  render()
</script>