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-heartbeatDé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");
// ... 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);siteIdrequisapiKeyrequisapiUrldisabledbatchbatchIntervaltrackCommand(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");
// ... rest of your middleware
}requestrequissiteIdrequisoptions.apiUrlCompatible 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 <dashboardUrl>Formats 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 quotidiennePulse 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érentsGestion 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"]
}
}
}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_timeseriesGestion 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.{
"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" }
}Temps réel et heartbeat
GET /api/analytics/liveNombre de visiteurs en direct. Retourne { live: number }. 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.
Analytique web
GET /api/analytics/overviewVisiteurs, pages vues, sessions + % de variationGET /api/analytics/pagesPages les plus visitées par nombre de visiteursGET /api/analytics/sourcesSources 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/botsTrafic robots/crawlersGET /api/analytics/countriesVisiteurs par paysGET /api/analytics/devicesRépartition par type d'appareilGET /api/analytics/browsersRépartition par navigateurGET /api/analytics/visitorsListe des visiteurs avec activitéGET /api/analytics/visitors/[visitorId]Profil du visiteur avec chronologie des événementsGET /api/analytics/eventsÉvénements personnalisésGET /api/analytics/campaignsAttribution 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/sessionsListe des sessionsAnalytique CLI
GET /api/analytics/cli-overviewInvocations, appelants, taux de succèsGET /api/analytics/cli-commandsCommandes les plus utiliséesGET /api/analytics/cli-timeseriesActivité CLI quotidienneAnalytique MCP
GET /api/analytics/mcp-overviewInvocations, appelants, taux de succèsGET /api/analytics/mcp-toolsOutils les plus utilisésGET /api/analytics/mcp-clientsRépartition par clientGET /api/analytics/mcp-timeseriesActivité MCP quotidienneAnalytique API
GET /api/analytics/api-overviewInvocations, appelants, taux d'erreurGET /api/analytics/api-endpointsEndpoints les plus sollicitésGET /api/analytics/api-timeseriesActivité API quotidienneClassements 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 SHA-256 avec la date actuelle incluse dans l'entrée du hachage. L'IP brute n'est jamais stockée. Les hachages de visiteurs quotidiens changent toutes les 24 heures. Un identifiant de visiteur stable (haché sans date) permet les profils de visiteurs au sein d'un site mais ne peut pas être inversé pour révéler 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.
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