Implémenter l'analytics Claude Code : GA4, GSC, Cloudflare et revenus
Implémentez l'analytics Claude Code avec GA4, GSC, Cloudflare, PV, CTA, revenus et tests.
L’analytics commence avant la balise
Implémenter l’analytics consiste à transformer PV, clics, lecture complète, demandes, clics produits et achats en données utiles. Une PV est une page vue. Un événement est une action enregistrée. Une conversion, ou key event GA4, est une action qui compte pour le business. Les UTM sont des marqueurs de campagne dans l’URL. Le consentement décide si le navigateur peut envoyer des données.
Claude Code est utile car il convertit un plan de mesure en code, tests et documentation. Dans l’exploitation du site de Masa, l’erreur a été de suivre la croissance des PV sans distinguer clics produit, formulaires terminés et inscriptions aux ressources gratuites. Le trafic semblait bon, mais le chemin vers le revenu restait invisible.
La pile utilisée ici est simple : GA4 pour campagnes et événements clés, Search Console pour requêtes et pages, Cloudflare pour signaux edge, Plausible pour objectifs légers, PostHog pour funnels. À lire aussi : SEO, A/B testing, performance et audit du funnel.
Plan de mesure
Demandez d’abord à Claude Code une table partant des décisions.
Prépare un plan analytics pour ce site de contenu.
L'objectif n'est pas seulement la croissance des PV, mais lecture complète, clics CTA, demandes, clics produit et achats.
Utilise business_question, event_name, trigger, required_params, provider, decision.
Utilise les événements recommandés GA4 quand ils conviennent. Les événements propres sont en snake_case.
| business_question | event_name | trigger | required_params | provider | decision |
|---|---|---|---|---|---|
| L’article est-il lu ? | article_read_complete | Footer visible à 70% | slug, category, reading_time_sec | GA4/PostHog | Réécrire intro, titres, liens |
| Les CTA sont-ils cliqués ? | cta_click | CTA produit, formation ou PDF | slug, cta_id, cta_type, target_url | GA4/Plausible/PostHog | Changer position et texte |
| La demande est-elle terminée ? | generate_lead | Formulaire envoyé avec succès | form_id, lead_source, value, currency | GA4/PostHog | Améliorer formulaire et offre |
| Les produits créent-ils une intention ? | purchase_link_click | Clic produit ou Gumroad | product_id, price, currency, slug | GA4/PostHog | Aligner article et produit |
| Quelles requêtes valent l’effort ? | gsc_query_page | Search Console API retourne page/query | page, query, clicks, impressions, ctr, position | GSC | Choisir titres et mises à jour |
| Les tags navigateur manquent-ils du trafic ? | edge_page_view | Cloudflare Worker reçoit une requête | path, country, status, duration_ms | Cloudflare | Détecter blocage et lenteur |
Vérifiez les événements GA4 dans recommended events. Utilisez generate_lead quand le sens correspond et gardez les événements propres pour les actions éditoriales.
flowchart LR
Reader["Lecteur"]
Consent["Consentement"]
Browser["browser analytics.js"]
Server["GA4 Measurement Protocol"]
GSC["Search Console API"]
Edge["Cloudflare Worker"]
Dashboard["Contenu, revenus, qualité"]
Reader --> Consent --> Browser
Browser --> Server
GSC --> Dashboard
Edge --> Dashboard
Browser --> Dashboard
Server --> Dashboard
Contrat d’événements
Le contrat évite les variantes de nom et force les paramètres nécessaires.
// event-plan.mjs
import { pathToFileURL } from "node:url";
export const eventPlan = {
article_read_complete: { required: ["slug", "category", "reading_time_sec"], providers: ["GA4", "PostHog"] },
cta_click: { required: ["slug", "cta_id", "cta_type", "target_url"], providers: ["GA4", "Plausible", "PostHog"] },
generate_lead: { required: ["form_id", "lead_source", "value", "currency"], providers: ["GA4", "PostHog"] },
purchase_link_click: { required: ["product_id", "price", "currency", "slug"], providers: ["GA4", "PostHog"] },
campaign_landing: { required: ["utm_source", "utm_medium", "utm_campaign"], providers: ["GA4"] },
};
export function validateEvent(name, params = {}) {
const contract = eventPlan[name];
if (!contract) return { ok: false, missing: ["known_event_name"] };
const missing = contract.required.filter((key) => params[key] === undefined || params[key] === "");
return { ok: missing.length === 0, missing };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
console.log(validateEvent("cta_click", { slug: "claude-code-analytics-implementation", cta_id: "products_footer", cta_type: "product", target_url: "/en/products/" }));
}
Séparez products et training. Le premier signale un intérêt pour des guides ou templates ; le second signale un besoin d’accompagnement d’équipe.
Couche navigateur
La couche navigateur gère consentement, UTM, nettoyage des paramètres et envoi aux fournisseurs.
// browser-analytics.js
const CONSENT_KEY = "analytics_consent";
const UTM_KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
function inBrowser() {
return typeof window !== "undefined" && typeof localStorage !== "undefined";
}
function hasConsent() {
return inBrowser() && localStorage.getItem(CONSENT_KEY) === "granted";
}
function cleanParams(params = {}) {
return Object.fromEntries(Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== "").map(([key, value]) => [key, typeof value === "boolean" ? Number(value) : value]));
}
export function setAnalyticsConsent(state) {
if (!inBrowser()) return;
localStorage.setItem(CONSENT_KEY, state);
window.gtag?.("consent", "update", { analytics_storage: state, ad_storage: "denied" });
}
export function readUtmParams() {
if (!inBrowser()) return {};
const current = new URLSearchParams(window.location.search);
const saved = JSON.parse(localStorage.getItem("landing_utm") || "{}");
const next = { ...saved };
for (const key of UTM_KEYS) {
const value = current.get(key);
if (value) next[key] = value;
}
localStorage.setItem("landing_utm", JSON.stringify(next));
return next;
}
export function trackEvent(name, params = {}) {
if (!hasConsent()) return;
const payload = cleanParams({ ...readUtmParams(), ...params });
window.gtag?.("event", name, payload);
window.plausible?.(name, { props: payload });
window.posthog?.capture(name, payload);
}
Envoyez generate_lead après réussite du formulaire. Pour la lecture complète, envoyez un seul événement lorsque la fin de l’article devient visible.
GA4, GSC et Cloudflare
Les résultats confirmés côté serveur passent par GA4 Measurement Protocol et doivent être testés avec validation server.
// ga4-server-event.mjs
import { pathToFileURL } from "node:url";
const { GA4_MEASUREMENT_ID, GA4_API_SECRET, GA4_DEBUG } = process.env;
if (!GA4_MEASUREMENT_ID || !GA4_API_SECRET) throw new Error("GA4_MEASUREMENT_ID and GA4_API_SECRET are required");
export async function sendGa4Event({ clientId, name, params = {} }) {
const endpoint = new URL(GA4_DEBUG === "1" ? "https://www.google-analytics.com/debug/mp/collect" : "https://www.google-analytics.com/mp/collect");
endpoint.searchParams.set("measurement_id", GA4_MEASUREMENT_ID);
endpoint.searchParams.set("api_secret", GA4_API_SECRET);
const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client_id: clientId, events: [{ name, params }] }) });
if (!response.ok) throw new Error("GA4 request failed with status " + response.status);
if (GA4_DEBUG === "1") {
const result = await response.json();
if (result.validationMessages?.length) throw new Error(JSON.stringify(result.validationMessages, null, 2));
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
await sendGa4Event({ clientId: "555.1234567890", name: "generate_lead", params: { form_id: "training", lead_source: "article_footer", value: 1, currency: "USD" } });
console.log("sent");
}
Search Console utilise Search Analytics API.
// gsc-query.mjs
import { pathToFileURL } from "node:url";
const { GSC_ACCESS_TOKEN, GSC_SITE_URL = "https://example.com/" } = process.env;
if (!GSC_ACCESS_TOKEN) throw new Error("GSC_ACCESS_TOKEN is required");
export async function querySearchConsole({ startDate, endDate, pageContains }) {
const endpoint = "https://www.googleapis.com/webmasters/v3/sites/" + encodeURIComponent(GSC_SITE_URL) + "/searchAnalytics/query";
const response = await fetch(endpoint, {
method: "POST",
headers: { authorization: "Bearer " + GSC_ACCESS_TOKEN, "content-type": "application/json" },
body: JSON.stringify({ startDate, endDate, dimensions: ["page", "query"], dimensionFilterGroups: pageContains ? [{ filters: [{ dimension: "page", operator: "contains", expression: pageContains }] }] : [], rowLimit: 25 }),
});
if (!response.ok) throw new Error("Search Console request failed with status " + response.status);
return response.json();
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const data = await querySearchConsole({ startDate: "2026-05-01", endDate: "2026-05-31", pageContains: "/blog/claude-code-analytics-implementation" });
console.log(JSON.stringify(data.rows ?? [], null, 2));
}
Pour les signaux edge, consultez Workers Analytics Engine et l’exemple writeDataPoint.
// cloudflare-worker.js
function json(data, status = 200) {
return new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json" } });
}
export default {
async fetch(request, env) {
if (request.method !== "POST") return json({ ok: false, error: "method_not_allowed" }, 405);
const event = await request.json().catch(() => null);
if (!event?.event_name || !event?.slug) return json({ ok: false, error: "event_name_and_slug_required" }, 400);
const country = request.cf?.country || request.headers.get("cf-ipcountry") || "XX";
env.ANALYTICS?.writeDataPoint({ blobs: [event.event_name, event.slug, event.cta_id || "", country], doubles: [Number(event.value || 1)], indexes: [String(event.slug).slice(0, 96)] });
return json({ ok: true });
},
};
Ne stockez pas email, nom, texte libre ou IP brute dans Cloudflare.
Cas d’usage et pièges
Cas 1 : SEO. Beaucoup d’impressions GSC et peu de CTR indiquent un problème de titre ou description. Beaucoup de PV et peu de lecture complète indiquent un problème d’introduction ou de structure. Cas 2 : produits. purchase_link_click avec product_id, price, currency et slug montre quels articles mènent aux offres. Cas 3 : formation. cta_click mesure l’intérêt, generate_lead mesure la demande réussie. Cas 4 : campagnes. Les UTM doivent survivre jusqu’au formulaire.
Pièges : noms incohérents, envoi avant consentement, doublons client/serveur, GSC traité comme journal complet, trop de scripts tiers et Core Web Vitals dégradés. Séparez les dashboards en contenu, revenus et qualité. Vérifiez GA4 DebugView, Realtime, Plausible Goals, PostHog Events et Cloudflare sous 24 heures.
Pour une configuration existante, products aide avec les templates et training convient si l’équipe veut revoir plan de mesure, code et tableaux de bord ensemble.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Échelle de sécurité des permissions Claude Code
Passer du read-only aux éditions limitées, preuves et checks de déploiement sans perdre le contrôle.
Claude Code Small PR Proof Pack : rendre les petits changements reviewables
Un pack de preuve pour PR Claude Code : diff, vérifications, URL publique, CTA et rollback.
Gate de review avant commit avec Claude Code
Review avant commit avec Claude Code : diff, build, URL publique, liens Gumroad, CTA consultation, tests manquants et fichiers hors scope.