11. Mesure d'audience
Comment brancher un script de suivi sur une application Rustwarden, et comment migrer un projet qui l'avait câblé en dur.
Pour la référence exhaustive de l'API, voir SERVICES_REFERENCE.md §23.
Le principe
Rustwarden ne connaît aucun fournisseur d'analytics. Il modélise le suivi comme
une URL de script plus des attributs data-*, ce qui suffit à décrire Rybbit,
Plausible, Umami, Matomo-tag et leurs équivalents. Chaque projet décide s'il
mesure son audience, avec quel script, et sous quel identifiant de site.
Par défaut, rien n'est activé : aucune balise rendue, CSP inchangée.
Trois responsabilités sont réparties ainsi :
| Quoi | Où | Pourquoi |
|---|---|---|
| L'URL du script | variable d'environnement | Elle alimente la CSP, figée au démarrage |
| Activation, identifiant, attributs | panneau d'administration | Modifiables sans redéploiement |
| L'endroit où la balise apparaît | vos templates | Le framework ne l'injecte pas tout seul |
Mise en place
1. Déclarer le script
# .env ANALYTICS_SCRIPT_URL=https://stats.example.org/api/script.js ANALYTICS_SITE_ID=1d870795f7ae ANALYTICS_OPT_OUT_STORAGE_KEY=disable-rybbit ANALYTICS_ENABLED=true
L'attribut portant l'identifiant dépend du fournisseur :
| Fournisseur | ANALYTICS_SITE_ID_ATTRIBUTE |
|---|---|
| Rybbit, Matomo-tag | data-site-id (défaut, rien à poser) |
| Umami | data-website-id |
| Plausible | data-domain |
2. Câbler le builder
use rustwarden_core::prelude::*; let app = RustwardenApp::builder() .from_env() .analytics(AnalyticsConfig::from_env()?) .build() .await?;
Ou en dur, sans passer par l'environnement :
.analytics( AnalyticsConfig::default() .with_script_url("https://stats.example.org/api/script.js") .with_site_id("1d870795f7ae") .with_opt_out_storage_key("disable-rybbit") .enabled(true), )
Vous n'avez rien à faire pour la CSP. build() ajoute l'origine du script à
script-src et connect-src. L'ordre des appels .analytics(..) et
.security_headers(..) est indifférent.
3. Poser la balise dans vos pages
Le framework ne touche pas à votre layout : c'est à vous d'appeler le helper, une fois, dans votre template de base.
// Handler pub async fn ma_page( analytics: AnalyticsContext, // .. ) -> Result<impl IntoResponse, AppError> { Ok(MaPageTemplate { analytics, /* .. */ }) }
<!-- base.html, avant </head> --> {% if analytics.is_enabled() %}{{ analytics.snippet|safe }}{% endif %}
Le |safe est légitime ici, et seulement ici : la valeur est produite par le
framework après validation par liste blanche et échappement HTML. N'y mettez
jamais de saisie utilisateur.
4. Offrir l'opposition au visiteur
let analytics_opt_out = AnalyticsOptOutContext::build(&analytics, &i18n_service, locale.as_str()).await;
<!-- Dans votre page « vie privée » --> {% include "components/analytics_opt_out.html" %}
Le composant ne rend rien si aucune clé d'opposition n'est configurée : vous pouvez l'inclure sans condition. Le choix du visiteur est stocké dans son navigateur et prend effet au chargement de page suivant, parce que la plupart des scripts ne lisent ce drapeau qu'à leur initialisation.
5. Piloter au quotidien
/admin/settings expose une section « Mesure d'audience » : interrupteur,
identifiant de site, attributs additionnels. Les changements prennent effet
immédiatement, sans redémarrage.
L'URL du script y apparaît en lecture seule. Ce n'est pas un oubli : la CSP est construite une fois au démarrage, donc une URL modifiable à chaud produirait un script bloqué par le navigateur. Cela évite aussi qu'un compte d'administration compromis puisse élargir la CSP vers un domaine arbitraire.
Migrer un projet qui l'avait en dur
Exemple type, tel qu'on le trouve dans un projet consommateur avant migration :
un partiel pm_analytics.html inclus dans une vingtaine de templates, un
composant d'opposition avec ses traductions écrites en ligne, et un middleware
qui réécrit la CSP entière pour autoriser le domaine du tracker.
Ce qui disparaît
| À supprimer | Remplacé par |
|---|---|
| Le partiel contenant la balise en dur | {{ analytics.snippet|safe }} dans le layout |
Les {% include %} répétés page par page | Un seul appel dans le template de base |
| Le middleware qui surcharge la CSP | build(), automatiquement |
La clé localStorage écrite en dur | ANALYTICS_OPT_OUT_STORAGE_KEY |
| Les traductions inline du composant d'opposition | analytics.opt_out.* |
Marche à suivre
- Relever la configuration existante dans le partiel : URL du script,
identifiant de site, attributs supplémentaires (
data-consent…). - Les porter en variables d'environnement, selon l'étape 1 ci-dessus.
- Câbler
.analytics(..)sur le builder. - Supprimer le partiel et tous ses
{% include %}, et poser l'appel unique dans le layout. C'est l'étape qui supprime le risque d'oubli sur une page. - Retirer la surcharge de CSP. Attention : si ce middleware ajoutait autre
chose que le domaine du tracker — typiquement
'unsafe-eval'pour Alpine.js — ne le supprimez pas aveuglément. Portez ce qui reste dans unSecurityHeadersConfigexplicite, et laissezbuild()s'occuper du seul domaine d'analytics. - Remplacer le composant d'opposition par
components/analytics_opt_out.html. - Vérifier, application lancée :
curl -sI http://localhost:3000/login | grep -i content-security-policy
L'origine doit apparaître dans script-src et connect-src. Puis, dans le
navigateur : la balise est présente, l'interrupteur d'/admin/settings la fait
disparaître sans redémarrage, et le bouton d'opposition empêche le script de
s'initialiser au rechargement suivant.
Limites
- Un seul fournisseur à la fois.
- Pas de bannière de consentement : un fournisseur qui en attend une se pilote par
un attribut (
data-consent), le recueil restant à la charge de l'application. - Pas de collecte côté serveur : le framework émet une balise, il ne mesure rien.
- Changer de domaine de collecte demande un redéploiement.
GitRust