# Suivi côté navigateur (`t.js`) — guide d'instrumentation Comment envoyer des données depuis un site web vers dataAnalytics. Ce document sert **à la fois de référence pour un développeur** et de **consignes pour une IA** chargée d'instrumenter un site. Le suivi passe par un unique script (`t.js`) : léger, asynchrone, **sans cookie**. Le comportement décrit ici est celui réellement implémenté dans `apps/ingestion/public/t.js` — voir aussi le contrat `docs/api-contract.md` §Ingestion. --- ## 1. Installation Le backoffice génère le snippet exact pour chaque site. Il se colle dans le `` : ```html ``` - La 1ʳᵉ ligne crée une **file d'attente** : les `da(...)` appelés avant le chargement du script ne sont pas perdus. - `data-site` (**obligatoire**) : le token public du site (fourni par le backoffice). - `data-endpoint` (optionnel) : URL de collecte personnalisée ; par défaut `origine-du-script/e`. Le domaine doit être dans l'**allowlist** du site (l'apex et `www` sont autorisés automatiquement à la création). Un événement provenant d'un autre domaine est ignoré silencieusement. ## 2. Ce qui est mesuré automatiquement — **rien à coder** Dès que le script est chargé : - **Pages vues** au chargement, **et** sur les navigations SPA (interception de `history.pushState` + `popstate`) — utile pour React/Vue/etc. - **UTM** lus depuis l'URL (`?utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`). - **Référent** (réduit à son hôte côté serveur), **langue** du navigateur, **largeur d'écran**. ⇒ Le trafic, les sources, les pays (via IP côté serveur, IP jetée ensuite), les appareils et les sessions remontent **sans instrumentation**. Le travail d'instrumentation ne concerne donc que les **objectifs** (conversions) et le **revenu**. ## 3. Objectifs (événements nommés) Un objectif est un **événement nommé**. Deux façons de le déclencher. ### a. Déclaratif — pour un clic (préféré, sans JavaScript) Ajouter `data-goal` (et éventuellement des `data-goal-`) sur l'élément cliquable : ```html ``` Au clic (capté par délégation, fonctionne aussi sur un enfant du bouton), ceci déclenche l'objectif `signup` avec les propriétés `{ plan: "pro", location: "hero" }` (les tirets des attributs deviennent des `_`). ### b. Programmatique — `da(name, props)` Pour un objectif déclenché par du code (après une réponse serveur, une étape validée…) : ```js da('signup') // objectif simple da('signup', { plan: 'pro', method: 'google' }) // avec propriétés da('scroll_depth', 80) // 2ᵉ argument NUMBER → champ `value` (entier) ``` > Règle : si le 2ᵉ argument est un **nombre**, il est envoyé dans `value` (ex. profondeur de scroll). > Si c'est un **objet**, ses paires deviennent des propriétés (`props`). **Quand utiliser quoi** : `data-goal` pour une **intention** (un clic) ; `da(...)` pour une **conversion confirmée** (après le succès réel — inscription validée, paiement confirmé sur la page de remerciement). Ne compte pas un « achat » sur le simple clic du bouton Payer. ## 4. Revenu Le revenu est une propriété **réservée** `revenue` (un nombre, en unité monétaire — €). Il est extrait dans un champ dédié (stocké en centimes côté serveur) ; les autres propriétés restent libres : ```js da('purchase', { revenue: 49.90, plan: 'pro' }) ``` À déclencher sur la **confirmation** de paiement (page de succès / webhook front), pas au clic. Le revenu est attribué à la source/UTM de la session en cours (cookieless, côté client). ## 5. Le contrat — limites à respecter | Champ | Règle | | --- | --- | | Nom d'objectif (`name`) | ≤ 64 caractères | | Propriétés (`props`) | **≤ 10 paires** ; clés ≤ 64 ; **valeurs = chaînes de caractères** ≤ 255 | | `revenue` | nombre ≥ 0, ≤ 1 000 000 | | `value` | entier (2ᵉ argument numérique) | Points importants : - **Les valeurs de propriété doivent être des chaînes.** `da('signup', { plan: 'pro' })` ✅ ; `da('signup', { seats: 5 })` ❌ (à écrire `{ seats: '5' }`). Un événement invalide est **rejeté silencieusement** (l'ingestion répond toujours `204`, mais rien n'est stocké). - **Nommage** : `snake_case`, minuscules, stable dans le temps (ex. `signup`, `add_to_cart`, `purchase`). Un nom = une ligne dans l'onglet **Conversions › Objectifs**. - **Cardinalité** : ne mets pas de valeurs uniques par utilisateur en propriété (id, email, timestamp). Utilise des **catégories** (`plan=pro`, `location=hero`) — c'est ce qui rend le drill-in par propriété lisible dans le backoffice. ## 6. Configuration par défaut (jeu d'objectifs standard) Pour obtenir des données exploitables **immédiatement**, instrumenter ce socle commun quand les éléments correspondants existent sur le site. Noms **canoniques** (à réutiliser tels quels) : | Objectif | Quand le déclencher | Propriétés suggérées | | --- | --- | --- | | `signup` | inscription / création de compte réussie | `plan`, `method` (google/email) | | `login` | connexion réussie | `method` | | `lead` | formulaire soumis (contact, démo, newsletter) | `form` (contact/newsletter/demo) | | `cta_click` | clic sur un appel à l'action clé | `location` (hero/nav/pricing/footer) | | `add_to_cart` | ajout au panier | `product` (catégorie, pas un id unique) | | `checkout_start` | début du paiement | — | | `purchase` | **paiement confirmé** | `revenue` (obligatoire), `plan` | | `contact` | clic sur email / téléphone | `channel` (email/phone) | Ce socle alimente directement le backoffice : onglet **Conversions** (compte + visiteurs par objectif, drill-in par propriété), **Revenu** (via `purchase`+`revenue`), **Acquisition/Campagnes** (via les UTM automatiques). On peut ensuite **épingler** un objectif comme « KPI n°1 » (carte en tête de la vue d'ensemble) — typiquement `signup` ou `purchase`. ## 7. Playbook pour une IA qui instrumente un site Objectif : poser la **configuration par défaut** (§6) sans instrumentation superflue. 1. **Ne rien coder pour les pages vues, sources, UTM, appareils** — c'est automatique (§2). 2. **Repérer les conversions** de la config par défaut présentes sur le site (bouton d'inscription, formulaire de contact, tunnel d'achat, CTA du hero…). 3. **Clics → déclaratif** : ajouter `data-goal=""` (+ `data-goal-` pour les catégories) sur l'élément. Préférer ça à du JS quand l'objectif = un clic. 4. **Succès confirmés → programmatique** : sur la page/état de succès réel, appeler `da('', { … })`. En particulier `da('purchase', { revenue: , plan: '' })` sur la page de confirmation de paiement — **jamais** au clic du bouton Payer. 5. **Respecter le contrat (§5)** : noms `snake_case`, ≤ 10 props, **valeurs de props en chaînes**, pas de valeurs à haute cardinalité. 6. **Ne pas ré-instrumenter** un objectif déjà couvert par un `data-goal` en ajoutant aussi un `da()` (double comptage). 7. En cas de doute sur un nom, réutiliser un nom de la config par défaut plutôt que d'en inventer un. ### Exemple minimal complet ```html Commencer ``` ## 8. Vérifier que ça marche, et dépanner **Vérification en 30 secondes** : installe le snippet, visite le site, puis ouvre le backoffice → onglet **Temps réel**. La visite doit apparaître en quelques secondes. Au tout premier événement reçu, le site passe automatiquement de « en attente » à **vérifié** et les statistiques se débloquent. **Vérifier depuis le navigateur** : onglet *Réseau* des outils de développement, filtre `t.js`. Tu dois voir le script se charger, puis une requête `POST` vers `/e` répondant **204**. Pour tester un objectif à la main, tape dans la console : `da('test_manuel')` → une nouvelle requête doit partir. **Point crucial — l'ingestion répond TOUJOURS `204`**, même quand elle rejette l'événement (c'est volontaire : ne donner aucun signal exploitable à un attaquant). Donc « `204` reçu » ne veut pas dire « enregistré ». Si rien n'apparaît dans le backoffice, les causes possibles sont : | Symptôme | Cause probable | | --- | --- | | Aucune requête `/e` dans l'onglet Réseau | Le snippet n'est pas chargé (vérifie le ``) | | `POST /e` en erreur `ERR_BLOCKED_BY_CLIENT` | Un bloqueur de publicité de **ton** navigateur ; la requête ne part pas. Rien à corriger côté site — vérifie en navigation privée sans extension | | Requêtes `204` mais rien dans le backoffice | Le domaine du site n'est pas dans l'**allowlist** (ex. site en `.com` alors que le site enregistré est en `.fr`), ou le `data-site` est erroné | | Les pages vues remontent, mais pas un objectif | Événement invalide : nom > 64 caractères, plus de 10 propriétés, ou **valeur de propriété qui n'est pas une chaîne** (voir §5) | | Rien depuis un sous-domaine (ex. `boutique.exemple.com`) | Ce sous-domaine doit être ajouté à l'allowlist du site (seuls l'apex et `www` le sont d'office) | **Bloqueurs de publicité** : une partie du trafic manquera toujours, quel que soit l'outil d'analytics. Le blocage a lieu **dans le navigateur du visiteur** — la requête ne part jamais (`ERR_BLOCKED_BY_CLIENT` dans la console) et n'atteint aucun serveur. Il n'y a donc rien à corriger côté installation, et la mesure ne peut pas « rattraper » ces visites. Le script et l'endpoint portent des noms neutres pour ne pas déclencher les règles génériques des listes de filtres (`/collect`, l'endpoint de Google Analytics, y figure tel quel) : ça limite la perte sans l'éliminer. En pratique, les **tendances restent fiables** — la perte est à peu près constante dans le temps — c'est le **niveau absolu** qui est sous-estimé, et davantage sur les audiences techniques et sur desktop que sur mobile.