duxt rapporte ce que les lecteurs font d’un site de documentation — chercher,
copier une commande, essayer un endpoint, répondre à « Cette page vous a-t-elle
été utile ? » — à **une seule fonction que vous écrivez**. Il n’embarque aucun
fournisseur, aucun SDK et aucun script, et un site qui n’a pas écrit cette
fonction ne rapporte rien et ne télécharge rien de plus.

::callout{type="info" title="Désactivé tant que vous ne l’activez pas"}
Il n’existe pas de destination par défaut, car une couche de documentation ne
peut pas en choisir une pour les lecteurs de quelqu’un d’autre. Sans
`duxt.analytics.track`, chacun des sites d’appel ci-dessous atteint une fonction
absente et s’arrête.
::

## Étapes

::steps

1. **Écrivez la fonction de rappel.** Elle vit dans `app.config.ts`, à côté du
   reste de votre configuration duxt, et on lui remet un événement par
   interaction :

   ```ts [app/app.config.ts]
   export default defineAppConfig({
     duxt: {
       analytics: {
         track(event) {
           // `event.name` est le discriminant ; `event.context` dit sur quelle
           // page, quelle locale, quelle source et quelle version cela s’est
           // produit.
           console.log(event.name, event.context.path);
         }
       }
     }
   });
   ```

   La fonction de rappel s’exécute **dans le navigateur uniquement**. Une
   compilation et un prérendu ne l’appellent jamais, si bien qu’un rendu
   statique ne remplit pas votre analytique de pages qu’un robot a ouvertes.

2. **Envoyez-le là où vit déjà votre analytique.** Ce qu’expose votre
   fournisseur — un SDK déjà présent sur la page, ou une route à vous pour que
   le navigateur du lecteur ne parle jamais à un tiers que vous n’avez pas
   choisi :

   ```ts [app/app.config.ts]
   export default defineAppConfig({
     duxt: {
       analytics: {
         track(event) {
           const { name, context, ...properties } = event;

           // Votre fournisseur, vos noms de propriétés, votre garde-fou de
           // consentement.
           window.plausible?.(name, { props: { ...context, ...properties } });
         }
       }
     }
   });
   ```

   ::callout{type="warning" title="Le consentement est le vôtre, pas celui de duxt"}
   duxt ne pose aucun cookie, ne stocke rien, ne tamponne rien et n’affiche
   aucune bannière de consentement — il n’a rien à demander, car rien ne quitte
   la page sinon par votre fonction. Cela signifie aussi que **le garde-fou est
   le vôtre** : si votre politique de confidentialité exige un consentement
   avant que cela n’atteigne un fournisseur, vérifiez-le dans `track` et sortez
   aussitôt.
   ::

3. **Choisissez les événements que vous voulez, et laissez tomber les autres.**
   L’union est fermée : un `switch` la couvre donc exhaustivement, et tout ce
   que vous ne nommez pas n’est tout simplement pas envoyé :

   ```ts [app/app.config.ts]
   track(event) {
     if (event.name === 'search' && event.results === 0) {
       // Le seul rapport qu’un site de documentation veut presque toujours :
       // un terme que quelqu’un a cherché et auquel le site n’a pas su
       // répondre.
       report('docs-search-miss', { query: event.query, ...event.context });
     }
   }
   ```

4. **Vérifiez qu’un événement arrive**, avec le contexte attendu — sous un
   préfixe de version et de locale, et pas seulement à l’URL nue.

::

## Les événements

Chaque événement a un `name`, ses propres champs et un `context`.

| `name` | Émis quand | Porte |
| :--- | :--- | :--- |
| `search` | Une requête non vide et temporisée se stabilise — y compris une qui n’a rien trouvé | `query`, `results`, `approximate` |
| `search-result` | Un lecteur ouvre un résultat | `query`, `rank`, `to`, `collection` |
| `copy` | Un exemple de code appartenant à la couche est copié | `kind`, `language`, `manager`, `sample` |
| `api-request` | Une requête d’essai se conclut | `operation`, `method`, `path`, `outcome`, `statusClass`, `duration` |
| `feedback` | Un lecteur répond à « Cette page vous a-t-elle été utile ? » | `helpful` |

`search` se déclenche **une fois par requête stabilisée**, pas à chaque frappe :
la temporisation écarte ce que personne n’a fini de taper, et un résultat
supplanté par un plus récent n’est jamais rapporté, car le lecteur ne l’a jamais
vu.

`copy` couvre les exemples de code propres à la couche — un bloc de code, un
`::code-group`, un bloc `::package-managers`, un échantillon de requête dans le
client d’essai — `kind` disant lequel. **« Copier la page en Markdown »
n’appartient pas à cette famille** et n’émet rien : c’est la page, pas un exemple
qui s’y trouve.

`api-request` décrit l’opération **telle que le document OpenAPI la déclare**.
`path` est le gabarit (`/pets/{id}`), jamais l’URL qui a été appelée.

### Le contexte

| Champ | Notes |
| :--- | :--- |
| `path` | Le chemin de documentation, sans le préfixe de locale, la chaîne de requête ni le fragment |
| `locale` | La locale dans laquelle la page a été lue |
| `source` | De quelle source vient la page — absent sur un site à source unique |
| `version` | Quelle version — absent là où la source n’en a qu’une |

## Ce qui n’est jamais envoyé

Cette liste est tout l’intérêt de la fonctionnalité, elle mérite donc d’être
énoncée clairement. Aucun événement ne porte :

- un identifiant d’API, un en-tête ou un jeton ;
- un corps de requête ou de réponse, ni une URL de requête concrète ;
- le texte qui a été copié ;
- le contenu affiché d’un résultat de recherche ;
- un identifiant de navigateur, de session ou de lecteur, de quelque sorte que
  ce soit ;
- la chaîne de requête ou le fragment du chemin propre à la page.

duxt ne **transmet, ne tamponne ni ne conserve** jamais rien lui-même. Il n’y a
dans la couche ni file d’attente, ni nouvelle tentative, ni stockage, ni appel
réseau : votre fonction est la seule chose qui quitte jamais la page.

Votre fonction de rappel n’est jamais attendue, et tout ce qu’elle lève ou avec
quoi elle rejette est avalé — un fournisseur en panne ne doit pas emporter avec
lui la recherche, la copie, le retour ou la requête.

## Les retours de page, dans les deux sens

`feedback` est la même réponse que `DuxtPageFeedback` a toujours émise.
L’événement `@feedback` du composant et son slot par défaut sont inchangés :
[l’envelopper](/guides/collect-page-feedback) fonctionne donc toujours, et
aucune des deux voies n’a besoin de l’autre — prenez la fonction de rappel
d’analytique si vous voulez une seule destination pour tout, et l’enveloppe si
les retours seuls doivent aller quelque part à eux.

## Liste de contrôle

- [ ] `duxt.analytics.track` est défini, et rien d’autre n’a été nécessaire pour
      l’activer
- [ ] La destination est la vôtre — un fournisseur que vous avez choisi, ou une
      route à vous
- [ ] Le consentement est vérifié dans `track`, si votre politique l’exige
- [ ] Un événement est arrivé avec le chemin, la locale, la source et la version
      attendus
- [ ] Rien dans ce que vous transmettez n’identifie le lecteur