Suivez tout.
Comprenez tout.
BetterMeter est une plateforme d'analytique respectueuse de la vie privée pour l'ère de l'IA. Suivez vos sites web, outils CLI, serveurs MCP et API -- le tout depuis un seul endroit.
Pour commencer
BetterMeter suit quatre types de sources. Chacune utilise une intégration légère qui envoie les événements au même pipeline respectueux de la vie privée.
Toutes les sources passent par le même pipeline : événement -> traitement (analyse du référent, détection de robots, géolocalisation, hachage de confidentialité) -> base de données. Vous consultez le tout dans le tableau de bord, le CLI ou via les outils MCP.
Suivi web
Ajoutez une seule balise script à votre site. Sans cookies, sans configuration CNAME, sans installation complexe. ~1,3 Ko compressé.
<script defer data-site="example.com" src="https://bettermeter.com/api/script"></script>Optionnel : ajoutez un pixel de suivi no-JS avant la balise de fermeture </body> pour détecter les robots et crawlers qui n'exécutent pas JavaScript :
<img src="https://bettermeter.com/api/pixel?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />Le script de suivi capture automatiquement les pages vues, la navigation SPA (History API) et les retours d'onglet. Pour les événements personnalisés et l'identification des utilisateurs :
// Track custom events
window.bettermeter.track("signup", { plan: "pro" });
// Identify users (optional)
window.bettermeter.identify("user_123");Attributs
data-siterequisdata-apidata-no-heartbeatdata-fingerprintdata-behaviorDétection de robots côté serveur
La plupart des robots (Googlebot, GPTBot, ClaudeBot, etc.) n'exécutent pas JavaScript, donc le script de suivi ne se déclenche jamais pour eux. Pour détecter le trafic des robots et crawlers, ajoutez une ligne à votre middleware Next.js :
import { reportBotVisit } from "@bettermeter/node/middleware";
export function middleware(request) {
reportBotVisit(request, "example.com", { apiKey: "bm_..." });
// ... rest of your middleware
}Installer le SDK :
npm install @bettermeter/nodeCompatible avec l'edge, non bloquant, sans impact sur la latence. Détecte plus de 25 robots connus, y compris les crawlers IA, les moteurs de recherche et les services de surveillance. Les visites de robots apparaissent automatiquement dans votre tableau de bord Crawlers.
Suivi CLI
Suivez comment les développeurs utilisent votre outil CLI. Voyez quelles commandes sont populaires, combien de temps elles prennent et quelles erreurs surviennent -- sans collecter de données personnelles.
npm install @bettermeter/nodeimport { BetterMeter } from "@bettermeter/node";
import { Command } from "commander";
const bm = new BetterMeter({
siteId: "my-cli-tool",
apiKey: "bm_...",
});
const program = new Command();
// Wraps all commands — tracks name, flags, exit code
bm.wrapCommander(program, { version: "1.0.0" });
program.command("deploy").action(() => { /* ... */ });
program.parse();
// Flush on exit
process.on("SIGTERM", () => bm.shutdown());bm.trackCommand({
command: "deploy",
subcommand: "preview",
flags: ["--prod", "--verbose"],
version: "2.1.0",
durationMs: 4500,
exitCode: 0,
isCi: !!process.env.CI,
});Ce qui est suivi
Uniquement les noms de commandes et de drapeaux. Les valeurs des drapeaux, les arguments et les chemins de fichiers ne sont jamais envoyés. Le SDK capture le système d'exploitation et l'architecture pour les analyses d'environnement.
Suivi MCP
Suivez quels outils les clients IA appellent sur votre serveur MCP, à quelle fréquence, quels clients (Claude, Cursor, Windsurf) génèrent le plus d'utilisation, et les taux d'erreur.
import { BetterMeter } from "@bettermeter/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const bm = new BetterMeter({
siteId: "my-mcp-server",
apiKey: "bm_...",
});
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
// Wraps server.tool() — auto-tracks every invocation
bm.wrapMcpServer(server);
// Tools registered after wrapping are automatically tracked
server.tool("search_docs", { query: z.string() }, async (args) => {
// ... your tool logic (unchanged)
});bm.trackTool({
tool: "search_docs",
client: "claude-code",
protocolVersion: "2024-11-05",
durationMs: 250,
success: true,
inputTokens: 150,
outputTokens: 800,
});Ce qui est suivi
Noms des outils, noms des clients, durée, succès/échec et compteurs de tokens optionnels. Les paramètres d'entrée et le contenu de sortie des outils ne sont jamais envoyés.
Suivi API
Suivez l'utilisation des endpoints API, la latence, les taux d'erreur et les profils d'utilisation avec le middleware Express ou des appels manuels.
import { BetterMeter } from "@bettermeter/node";
import express from "express";
const bm = new BetterMeter({
siteId: "my-api",
apiKey: "bm_...",
});
const app = express();
// Auto-track all requests
app.use(bm.expressMiddleware());bm.trackApi({
method: "POST",
endpoint: "/api/users", // Use patterns, not actual paths with IDs
statusCode: 201,
durationMs: 45,
});Ce qui est suivi
Méthode HTTP, modèle d'endpoint, code de statut et durée. Les corps de requête/réponse, les en-têtes, les paramètres de requête et les paramètres de chemin ne sont jamais envoyés. Utilisez des modèles d'endpoint (/api/users/:id) et non des chemins réels (/api/users/abc123).
Référence SDK
Le SDK @bettermeter/node est sans dépendance (utilise fetch et crypto intégrés). Nécessite Node.js 18+.
Constructeur
const bm = new BetterMeter(config);siteIdrequisapiKeyrequisapiUrlallowCustomApiUrldisabledbatchbatchIntervalhashUserIdredactPropertiesLes valeurs par défaut de sécurité sont conservatrices : les clés API sont requises pour l'ingestion SDK, les valeurs de flags sont supprimées, les propriétés personnalisées ne peuvent pas remplacer les champs SDK de confiance, les identifiants utilisateur sont hachés par défaut et les clés de propriétés sensibles sont masquées avant l'envoi.
trackCommand(options)
commandrequissubcommandflagsversiondurationMsexitCodeisCiuserIdpropertiestrackTool(options)
toolrequisclientprotocolVersiondurationMssuccesserrorTypeinputTokensoutputTokensuserIdpropertiestrackApi(options)
methodrequisendpointrequisstatusCodedurationMsuserIdpropertiesEncapsuleurs automatiques
wrapCommander(program, options?)
S'accroche au postAction de Commander.js pour suivre automatiquement toutes les commandes.
wrapMcpServer(server)
Patch dynamique de server.tool() pour encapsuler tous les gestionnaires avec chronométrage et suivi des erreurs.
expressMiddleware()
Retourne un middleware Express/Connect qui suit chaque requête sur res.end.
Détection de robots côté serveur
La plupart des robots n'exécutent pas JavaScript, donc le script de suivi ne se déclenche jamais pour eux. Utilisez reportBotVisit() dans votre middleware serveur pour détecter les robots au niveau de la requête.
// Next.js middleware
import { reportBotVisit } from "@bettermeter/node/middleware";
export function middleware(request) {
reportBotVisit(request, "my-site.com", { apiKey: "bm_..." });
// ... rest of your middleware
}requestrequissiteIdrequisoptions.apiUrloptions.apiKeyoptions.allowCustomApiUrlCompatible avec l'edge (sans dépendances Node.js). Non bloquant -- n'ajoute aucune latence aux réponses. Ignore automatiquement les ressources statiques, les routes API et les fichiers internes Next.js.
Cycle de vie
flush(): Promise<void>
Envoyer immédiatement tous les événements en file d'attente.
shutdown(): Promise<void>
Arrêter le minuteur de lot et vider les événements restants. Appeler avant la fin du processus.
Référence CLI
Le CLI BetterMeter vous permet d'interroger les analyses depuis le terminal avec un affichage visuel élégant. Toutes les commandes acceptent -s/--site, -r/--range, -l/--limit et --json.
npm install -g bettermeter
bettermeter login -t <apiKey> -u https://bettermeter.comFormats de sortie
Par défaut, le CLI affiche un rendu visuel riche avec des graphiques ASCII, du texte coloré, des sparklines et des cartes de statistiques. Toute la sortie utilise des caractères Unicode compatibles avec tous les terminaux modernes. Les couleurs détectent automatiquement les capacités du terminal et respectent la variable d'environnement NO_COLOR.
Par défaut (visuel)Graphiques linéaires, barres horizontales, sparklines, tableaux stylisés avec couleurs--jsonDonnées JSON brutes -- idéal pour le scripting, le piping vers jq ou l'utilisation programmatiqueLa sortie visuelle inclut :
- Graphiques linéaires pour les données temporelles (visiteurs quotidiens, invocations)
- Barres horizontales pour les listes classées (pages, sources, pays)
- Sparklines intégrées aux statistiques d'ensemble pour une visualisation rapide des tendances
- Cartes de statistiques avec indicateurs de variation colorés pour les vues d'ensemble
- Tableaux stylisés avec bordures pour les données détaillées
# Visual output (default)
bettermeter stats -s example.com
# JSON output for scripting
bettermeter stats -s example.com --json | jq '.visitors'Authentification
login -t <key> -u <url>S'authentifier avec une clé API et l'URL du tableau de bordlogoutSupprimer les identifiants enregistréswhoamiAfficher l'utilisateur actuellement authentifiéTemps réel
live -s <siteId>Nombre de visiteurs en direct (--json pour la sortie brute)Analytique web
statsAperçu : visiteurs, pages vues, sessions + % de variationpagesPages les plus visitées par nombre de visiteurssourcesSources de trafic (--filter all|ai|traditional)ai-trafficRépartition des référencements IA par plateformebotsTrafic robots/crawlers (--category all|ai-crawler|search|monitoring|scraper)timeseriesTendance quotidienne visiteurs/pages vuescountriesVisiteurs par paysdevicesRépartition par appareilbrowsersRépartition par navigateurvisitorsVisiteurs récents avec résumé d'activitéeventsÉvénements personnalisés avec compteurscampaignsAttribution des URL de campagne (UTM + identifiants de clic)channelsRépartition par canal (Direct, Recherche payante, Organique, etc.)exportRapport complet (--format json|csv|md)Analytique CLI
cli-overviewInvocations, appelants, taux de succès, durée moyennecli-commandsCommandes les plus utilisées par nombre d'invocationscli-timeseriesActivité CLI quotidienneAnalytique MCP
mcp-overviewInvocations, appelants, taux de succès, durée moyennemcp-toolsOutils MCP les plus utilisés par nombre d'invocationsmcp-clientsRépartition par client (Claude, Cursor, etc.)mcp-timeseriesActivité MCP quotidienneAnalytique API
api-overviewInvocations, appelants, taux d'erreur, durée moyenneapi-endpointsEndpoints les plus sollicités par nombre d'invocationsapi-timeseriesActivité API quotidienneBuilder
dashboardsLister les mises en page de dashboards enregistrées affichées dans la navigation latéraledashboard:createCréer une mise en page de dashboard personnaliséedashboard:deleteSupprimer une mise en page de dashboard personnaliséereportsLister les modèles de rapport enregistrésreport:createCréer un modèle de rapportreport:previewPrévisualiser un modèle de rapport sans l'enregistrerreport:deleteSupprimer un modèle de rapportPulse AI
pulse insightsAnomalies, tendances, opportunités, jalonspulse healthScore de santé produit (0-100, note A-F)pulse briefingBriefing quotidien/hebdomadaire (-p/--period daily|weekly)pulse forecastPrévision de trafic (-m/--metric, -d/--days)pulse compareComparer deux périodes (-r/--range, --from2, --to2)pulse alertsLister les règles d'alerte de surveillancepulse alerts:createCréer une alerte (-t/--type, -n/--name, -c/--condition)pulse alerts:deleteSupprimer une alerte (-i/--id)pulse notificationsNotifications récentes (--unread pour les non lues uniquement)Classements de recherche et visibilité IA
brand-report <domain>Générer un rapport de classements de recherche (-q/--queries)brand-config <domain>Voir/mettre à jour la configuration de surveillance de marquebrand-compare <domain>Comparer les classements avec les concurrents (-q, -c)brand-alerts <domain>Gérer les alertes de classement (-a list|create|delete)ai-mentions <domain>Mentions de marque par chatbots IA (-q/--queries, -p/--providers)backlinks <domain>Profil de backlinks : rang du domaine, domaines référentsLes scans externes payants acceptent 1 à 10 requêtes par demande. La comparaison concurrentielle accepte jusqu'à 10 concurrents.
Gestion des sites
sites listLister tous les sitessites add <domain>Ajouter un nouveau site (-n/--name pour le nom d'affichage)sites remove <siteId>Supprimer un sitesites info <siteId>Afficher les détails du site et le code de suiviinstall <siteId>Obtenir le code de suivi pour un siteGestion de l'équipe
members list -s <siteId>Lister les membres du sitemembers add <email>Ajouter un membre (-s, -r viewer|editor|admin, --all-sites)members remove <id>Supprimer un membre (-s)members update-role <id>Mettre à jour le rôle d'un membre (-s, -r)Facturation
billingAfficher le forfait actuel, l'utilisation et les informations de facturation (--json)Options
-s, --siterequis-r, --range-l, --limit--jsonRéférence des outils MCP
BetterMeter fonctionne comme un serveur MCP pour les assistants IA. Chaque commande CLI a un outil MCP correspondant avec le préfixe bettermeter_.
{
"mcpServers": {
"bettermeter": {
"command": "npx",
"args": ["bettermeter"]
}
}
}Les outils MCP qui modifient des données exigent une phrase de confirmation exacte (CONFIRM <action> <target>) pour l'action et la cible spécifiques avant de créer, mettre à jour, supprimer, scanner ou lancer des requêtes payantes.
Tous les outils d'analytique acceptent site_id (string), range (today|7d|30d|90d|12m) et limit (number). L'outil MCP bettermeter_export supporte les formats json et md (la commande CLI export supporte aussi csv). Exemples d'utilisation pour les assistants IA :
bettermeter_live_visitorsbettermeter_statsbettermeter_ai_trafficbettermeter_cli_commandsbettermeter_mcp_clientsbettermeter_api_endpoints with range=7dbettermeter_pulse_healthbettermeter_pulse_insightsbettermeter_pulse_briefing with period=weeklyListe complète des outils
Temps réel
bettermeter_live_visitorsAnalytique web
bettermeter_statsbettermeter_pagesbettermeter_sourcesbettermeter_ai_trafficbettermeter_botsbettermeter_timeseriesbettermeter_countriesbettermeter_devicesbettermeter_browsersbettermeter_visitorsbettermeter_visitorbettermeter_eventsbettermeter_campaignsbettermeter_channelsbettermeter_exportAnalytique CLI
bettermeter_cli_overviewbettermeter_cli_commandsbettermeter_cli_timeseriesAnalytique MCP
bettermeter_mcp_overviewbettermeter_mcp_toolsbettermeter_mcp_clientsbettermeter_mcp_timeseriesAnalytique API
bettermeter_api_overviewbettermeter_api_endpointsbettermeter_api_timeseriesBuilder
bettermeter_dashboardsbettermeter_create_dashboardbettermeter_delete_dashboardbettermeter_reportsbettermeter_create_reportbettermeter_report_previewbettermeter_delete_reportGestion des sites
bettermeter_sites_listbettermeter_sites_addbettermeter_sites_removebettermeter_sites_infobettermeter_installGestion de l'équipe
bettermeter_members_listbettermeter_members_addbettermeter_members_removebettermeter_members_update_roleClassements de recherche et visibilité IA
bettermeter_brand_reportbettermeter_brand_configbettermeter_brand_comparebettermeter_brand_alertsbettermeter_ai_mentionsbettermeter_backlinksFacturation
bettermeter_billingPulse AI
bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notificationsRéférence API
Tous les endpoints d'analytique acceptent les requêtes GET avec des paramètres de requête. Authentifiez-vous avec Authorization: Bearer <api_key>.
Ingestion d'événements
POST /api/eventIngérer un événement (web, CLI, MCP ou API). Retourne 202.Les événements web doivent référencer un site enregistré et l'URL de l'événement doit correspondre au domaine du site. Les événements CLI, MCP et API exigent Authorization: Bearer [api_key]; les en-têtes user-agent et client-IP transférés ne sont approuvés qu'après validation de cette clé pour le site.
{
"site_id": "example.com",
"event_name": "pageview", // or "cli.command", "mcp.tool", "api.request"
"event_source": "web", // "web" | "cli" | "mcp" | "api"
"url": "https://example.com/page",
"pathname": "/page",
"hostname": "example.com",
"referrer": "https://google.com",
"screen_width": 1920,
"timezone": "America/New_York",
"user_id": "optional_user_id",
"properties": { "key": "value" }
}Intérêt d'inscription
POST /api/waitlistCapturer les inscriptions publiques à la liste d'attente, enregistrer le prospect et envoyer un courriel à farbour@paraito.ca.Temps réel et heartbeat
GET /api/analytics/liveNombre de visiteurs en direct. Retourne un champ numérique live. Accepte ?siteId=...POST /api/heartbeatRecevoir les heartbeats du navigateur pour le suivi des visiteurs en directPOST /api/hAlias furtif pour /api/heartbeat (résistant aux bloqueurs de publicités)Endpoints de requête
Tous acceptent ?siteId=...&from=YYYY-MM-DD&to=YYYY-MM-DD. Les endpoints de liste acceptent aussi limit=all lorsque vous avez besoin du jeu complet pour la pagination ou la recherche.
Analytique web
GET /api/analytics/overviewVisiteurs, pages vues, sessions + % de variationGET /api/analytics/pages?limit=allPages par nombre de visiteurs. Ajoutez limit=all pour retourner la liste complète consultable.GET /api/analytics/sources?limit=allSources de trafic avec détection IAGET /api/analytics/timeseriesTendance quotidienne visiteurs/pages vuesGET /api/analytics/ai-trafficRépartition des référencements IA par plateformeGET /api/analytics/bots?limit=allTrafic robots/crawlersGET /api/analytics/countries?limit=allVisiteurs par paysGET /api/analytics/devicesRépartition par type d'appareilGET /api/analytics/browsers?limit=allRépartition par navigateurGET /api/analytics/visitors?limit=allListe des visiteurs avec activitéGET /api/analytics/visitors/[visitorId]Profil du visiteur avec chronologie des événementsGET /api/analytics/events?limit=allÉvénements personnalisésGET /api/analytics/campaigns?limit=allAttribution des URL de campagne (UTM + identifiants de clic)GET /api/analytics/campaigns/[campaign]Détail d'une seule campagneGET /api/analytics/channelsRépartition par canalGET /api/analytics/session-statsStatistiques de sessionsGET /api/analytics/sessions?limit=allListe des sessionsAnalytique CLI
GET /api/analytics/cli-overviewInvocations, appelants, taux de succèsGET /api/analytics/cli-commands?limit=allCommandes les plus utiliséesGET /api/analytics/cli-timeseriesActivité CLI quotidienneAnalytique MCP
GET /api/analytics/mcp-overviewInvocations, appelants, taux de succèsGET /api/analytics/mcp-tools?limit=allOutils les plus utilisésGET /api/analytics/mcp-clients?limit=allRépartition par clientGET /api/analytics/mcp-timeseriesActivité MCP quotidienneAnalytique API
GET /api/analytics/api-overviewInvocations, appelants, taux d'erreurGET /api/analytics/api-endpoints?limit=allEndpoints les plus sollicitésGET /api/analytics/api-timeseriesActivité API quotidienneBuilder
GET /api/builders/dashboardsLister les dashboards personnalisés enregistrés d'un site et son menu latéral de dashboardsPOST /api/builders/dashboardsCréer un dashboard personnaliséGET /api/builders/dashboards/[dashboardId]Obtenir un dashboard personnaliséPATCH /api/builders/dashboards/[dashboardId]Mettre à jour un dashboard personnaliséDELETE /api/builders/dashboards/[dashboardId]Supprimer un dashboard personnaliséGET /api/builders/reportsLister les modèles de rapport enregistrésPOST /api/builders/reportsCréer un modèle de rapportGET /api/builders/reports/[reportId]Obtenir un modèle de rapportPATCH /api/builders/reports/[reportId]Mettre à jour un modèle de rapportDELETE /api/builders/reports/[reportId]Supprimer un modèle de rapportPOST /api/builders/reports/previewPrévisualiser le plan d'un rapportClassements de recherche et visibilité IA
GET /api/analytics/brandRapport de visibilité des classements de rechercheGET /api/analytics/brand/historyDonnées historiques de classementGET /api/analytics/brand/compareComparaison avec les concurrentsGET /api/analytics/brand/alertsRègles d'alerte de classementGET /api/analytics/brand/exportExporter les données de classementGET /api/analytics/backlinksProfil de backlinksGET /api/analytics/ai-mentionsMentions de marque par chatbots IAGET /api/analytics/ai-mentions/historyHistorique des mentions IAPulse AI
GET /api/pulse/insightsAnomalies, tendances, opportunitésGET /api/pulse/healthScore de santé produit (0-100)GET /api/pulse/briefingBriefing quotidien ou hebdomadaireGET /api/pulse/forecastPrévision de trafic/utilisationGET /api/pulse/compareComparaison de périodesGET /api/pulse/alertsLister les alertes de surveillancePOST /api/pulse/alertsCréer une règle d'alerteDELETE /api/pulse/alertsSupprimer une règle d'alerteGET /api/pulse/notificationsObtenir les notificationsPATCH /api/pulse/notificationsMarquer les notifications comme luesPOST /api/pulse/chatChat Pulse AI (streaming)Pulse AI
Pulse AI est la couche d'intelligence analytique proactive de BetterMeter. Au lieu d'attendre que vous consultiez les tableaux de bord, Pulse détecte automatiquement les anomalies, fait remonter les insights, évalue la santé du produit, prédit les tendances et livre des briefings quotidiens ou hebdomadaires.
Capacités
Types d'alertes
Créez des alertes de surveillance qui génèrent des notifications lorsque les conditions sont remplies :
traffic_spikeAlerte lorsque le trafic dépasse un seuiltraffic_dropAlerte lorsque le trafic descend sous un seuilerror_rateAlerte lorsque le taux d'erreur dépasse un seuilbot_surgeAlerte en cas de pics inhabituels d'activité de robots/crawlersnew_ai_crawlerAlerte lorsqu'un nouveau crawler IA est détectéperformanceAlerte en cas de dégradation des performancesEndpoints API
GET /api/pulse/insightsAnomalies, tendances, opportunités, jalonsGET /api/pulse/healthScore de santé produit (0-100) avec répartition par catégorieGET /api/pulse/briefingRésumé de briefing quotidien ou hebdomadaireGET /api/pulse/forecastPrévision de trafic/utilisation avec intervalles de confianceGET /api/pulse/compareComparaison approfondie période par périodeGET /api/pulse/alertsLister les règles d'alerte de surveillancePOST /api/pulse/alertsCréer une nouvelle règle d'alerteDELETE /api/pulse/alertsSupprimer une règle d'alerteGET /api/pulse/notificationsObtenir les notifications récentesPATCH /api/pulse/notificationsMarquer les notifications comme luesCommandes CLI
pulse insightsObtenir les insights proactifs et les anomaliespulse healthScore de santé produit avec notepulse briefingBriefing quotidien ou hebdomadaire (-p/--period daily|weekly)pulse forecastPrévision de trafic (-m/--metric, -d/--days)pulse compareComparer deux périodes (-r/--range, --from2, --to2)pulse alertsLister les alertes de surveillancepulse alerts:createCréer une alerte (-t/--type, -n/--name, -c/--condition)pulse alerts:deleteSupprimer une alerte (-i/--id)pulse notificationsNotifications récentes (--unread pour les non lues uniquement)Outils MCP
bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notificationsConfiguration du proxy
Les bloqueurs de publicités bloquent parfois les scripts d'analytique tiers. En relayant BetterMeter via votre propre domaine, le script et les événements apparaissent comme des requêtes de première partie -- les rendant invisibles aux bloqueurs. Cela fonctionne car le navigateur voit des requêtes vers yourdomain.com/bm/... au lieu de bettermeter.com/api/....
Comment ça fonctionne
Vous configurez des réécritures d'URL sur votre serveur pour que trois chemins de votre domaine redirigent vers BetterMeter :
/bm/s→ https://bettermeter.com/api/sScript de suivi/bm/e→ https://bettermeter.com/api/eEndpoint d'événements/bm/p→ https://bettermeter.com/api/pPixel noscriptEnsuite, mettez à jour votre code de suivi pour utiliser les chemins du proxy :
<script defer data-site="example.com" data-api="/bm/e" src="/bm/s"></script>
<img src="/bm/p?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />L'attribut data-api indique au script d'envoyer les événements à votre endpoint proxy au lieu de directement à BetterMeter. Le src charge le script depuis votre proxy. Pour le navigateur (et les bloqueurs de publicités), tout est une requête de même origine.
Guides par plateforme
Next.js
module.exports = {
async rewrites() {
return [
{ source: "/bm/s", destination: "https://bettermeter.com/api/s" },
{ source: "/bm/e", destination: "https://bettermeter.com/api/e" },
{ source: "/bm/p", destination: "https://bettermeter.com/api/p" },
];
},
};Nuxt
export default defineNuxtConfig({
routeRules: {
"/bm/s": { proxy: "https://bettermeter.com/api/s" },
"/bm/e": { proxy: "https://bettermeter.com/api/e" },
"/bm/p": { proxy: "https://bettermeter.com/api/p" },
},
});SvelteKit
const config = {
kit: {
// SvelteKit doesn't have built-in rewrites.
// Use a server hook instead:
},
};import type { Handle } from "@sveltejs/kit";
const PROXY_MAP: Record<string, string> = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
export const handle: Handle = async ({ event, resolve }) => {
const target = PROXY_MAP[event.url.pathname];
if (target) {
const url = new URL(target);
url.search = event.url.search;
const res = await fetch(url, {
method: event.request.method,
headers: event.request.headers,
body: event.request.method !== "GET" ? event.request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
return new Response(res.body, {
status: res.status,
headers: res.headers,
});
}
return resolve(event);
};Astro
import type { APIRoute } from "astro";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
export const ALL: APIRoute = async ({ params, request }) => {
const target = PROXY_MAP[params.proxy ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
};Remix / React Router
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
async function proxy({ request, params }: LoaderFunctionArgs | ActionFunctionArgs) {
const target = PROXY_MAP[params["*"] ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
}
export const loader = proxy;
export const action = proxy;Nginx
location /bm/s {
proxy_pass https://bettermeter.com/api/s;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/e {
proxy_pass https://bettermeter.com/api/e;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/p {
proxy_pass https://bettermeter.com/api/p;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}Apache
RewriteEngine On
RewriteRule ^bm/s$ https://bettermeter.com/api/s [P,L]
RewriteRule ^bm/e$ https://bettermeter.com/api/e [P,L]
RewriteRule ^bm/p$ https://bettermeter.com/api/p [P,L]
# Requires mod_proxy and mod_proxy_http enabled:
# a2enmod proxy proxy_httpCaddy
example.com {
handle_path /bm/s {
reverse_proxy https://bettermeter.com/api/s {
header_up Host bettermeter.com
}
}
handle_path /bm/e {
reverse_proxy https://bettermeter.com/api/e {
header_up Host bettermeter.com
}
}
handle_path /bm/p {
reverse_proxy https://bettermeter.com/api/p {
header_up Host bettermeter.com
}
}
}Cloudflare Workers
export default {
async fetch(request) {
const url = new URL(request.url);
const map = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
const target = map[url.pathname];
if (!target) return fetch(request);
const dest = new URL(target);
dest.search = url.search;
return fetch(dest.toString(), {
method: request.method,
headers: { ...Object.fromEntries(request.headers), Host: "bettermeter.com" },
body: request.method !== "GET" ? request.body : undefined,
});
},
};Vercel (without Next.js)
{
"rewrites": [
{ "source": "/bm/s", "destination": "https://bettermeter.com/api/s" },
{ "source": "/bm/e", "destination": "https://bettermeter.com/api/e" },
{ "source": "/bm/p", "destination": "https://bettermeter.com/api/p" }
]
}Netlify
[[redirects]]
from = "/bm/s"
to = "https://bettermeter.com/api/s"
status = 200
force = true
[[redirects]]
from = "/bm/e"
to = "https://bettermeter.com/api/e"
status = 200
force = true
[[redirects]]
from = "/bm/p"
to = "https://bettermeter.com/api/p"
status = 200
force = trueWordPress
Ajoutez ces règles de réécriture au functions.php de votre thème ou à un plugin personnalisé :
add_action('init', function() {
add_rewrite_rule('^bm/s$', 'index.php?bm_proxy=s', 'top');
add_rewrite_rule('^bm/e$', 'index.php?bm_proxy=e', 'top');
add_rewrite_rule('^bm/p$', 'index.php?bm_proxy=p', 'top');
});
add_filter('query_vars', function($vars) {
$vars[] = 'bm_proxy';
return $vars;
});
add_action('template_redirect', function() {
$proxy = get_query_var('bm_proxy');
if (!$proxy) return;
$map = [
's' => 'https://bettermeter.com/api/s',
'e' => 'https://bettermeter.com/api/e',
'p' => 'https://bettermeter.com/api/p',
];
if (!isset($map[$proxy])) return;
$url = $map[$proxy];
if ($_SERVER['QUERY_STRING']) $url .= '?' . $_SERVER['QUERY_STRING'];
$args = ['method' => $_SERVER['REQUEST_METHOD'], 'timeout' => 10];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$args['body'] = file_get_contents('php://input');
$args['headers'] = ['Content-Type' => $_SERVER['CONTENT_TYPE'] ?? 'application/json'];
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) { status_header(502); exit; }
status_header(wp_remote_retrieve_response_code($response));
foreach (wp_remote_retrieve_headers($response) as $k => $v) {
if (in_array(strtolower($k), ['content-type', 'cache-control'])) header("$k: $v");
}
echo wp_remote_retrieve_body($response);
exit;
});
// After adding, flush rewrite rules: Settings → Permalinks → SaveDjango
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("bm/<slug:endpoint>", views.bm_proxy),
]
# views.py
import requests
from django.http import HttpResponse
PROXY_MAP = {
"s": "https://bettermeter.com/api/s",
"e": "https://bettermeter.com/api/e",
"p": "https://bettermeter.com/api/p",
}
def bm_proxy(request, endpoint):
target = PROXY_MAP.get(endpoint)
if not target:
return HttpResponse("Not found", status=404)
url = target + ("?" + request.META["QUERY_STRING"] if request.META.get("QUERY_STRING") else "")
resp = requests.request(
method=request.method, url=url,
headers={"Host": "bettermeter.com", "Content-Type": request.content_type},
data=request.body if request.method == "POST" else None,
timeout=10,
)
return HttpResponse(resp.content, status=resp.status_code, content_type=resp.headers.get("Content-Type"))Rails
# config/routes.rb
match "/bm/:endpoint", to: "bm_proxy#proxy", via: [:get, :post], constraints: { endpoint: /[sep]/ }
# app/controllers/bm_proxy_controller.rb
class BmProxyController < ApplicationController
skip_before_action :verify_authenticity_token
PROXY_MAP = { "s" => "/api/s", "e" => "/api/e", "p" => "/api/p" }.freeze
def proxy
path = PROXY_MAP[params[:endpoint]]
return head(:not_found) unless path
uri = URI("https://bettermeter.com#{path}")
uri.query = request.query_string.presence
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = (request.post? ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
req["Host"] = "bettermeter.com"
if request.post?
req.body = request.body.read
req["Content-Type"] = request.content_type
end
resp = http.request(req)
render body: resp.body, status: resp.code.to_i, content_type: resp["Content-Type"]
end
endVérification
Après avoir configuré le proxy, vérifiez qu'il fonctionne :
# Should return the tracker JavaScript
curl -s https://yourdomain.com/bm/s | head -5
# Should return 202 (event accepted)
curl -s -o /dev/null -w "%{http_code}" -X POST https://yourdomain.com/bm/e \
-H "Content-Type: application/json" \
-d '{"site_id":"yourdomain.com","event_name":"test"}'Modèle de données
Les quatre sources d'événements partagent une seule table Event. La colonne eventSource les distingue. Les métadonnées spécifiques à chaque source sont dans la colonne JSON properties.
Correspondance des champs
Confidentialité
BetterMeter est conçu dès le départ pour respecter la vie privée des utilisateurs. Aucune bannière de consentement requise. Conforme au RGPD par défaut.
Sans cookies
Zéro cookie, zéro stockage local, zéro fingerprinting. Le script de suivi est sans état.
Identifiants hachés
Les adresses IP sont hachées en HMAC-SHA-256 avec un secret visiteur côté serveur. L'IP brute n'est jamais stockée. Les hachages de visiteurs quotidiens changent toutes les 24 heures, tandis qu'un hachage stable par site permet les profils de visiteurs sans exposer l'IP d'origine.
Pas de collecte de données personnelles
Le SDK suit les noms de commandes, les noms d'outils et les modèles d'endpoints -- jamais les arguments, les chemins de fichiers, les corps de requête ou les données personnelles. Les identifiants utilisateur sont hachés par défaut et les clés de propriétés sensibles sont masquées avant l'envoi.
Envoi et oubli
Les appels d'analytique sont asynchrones et ne lèvent jamais d'exception. Un échec réseau abandonne silencieusement l'événement. L'analytique ne doit jamais interrompre l'application hôte.
Désactivation
Définissez disabled: true dans la configuration du SDK ou BETTERMETER_DISABLED=1 comme variable d'environnement.
Pipeline ouvert
Chaque événement passe par la même fonction processEvent(). Vous pouvez auditer exactement ce qui est collecté et stocké.
BetterMeter Analytics -- Conçu pour le nouvel internet