Claude Code Analytics 구현: GA4, GSC, Cloudflare로 PV와 수익 보기
Claude Code로 GA4, GSC, Cloudflare 분석을 설계하고 PV, CTA, 수익 이벤트를 검증합니다.
Analytics 구현은 태그 설치보다 앞선다
Analytics 구현은 PV, 클릭, 글 완독, 문의, 상품 링크, 구매 경로를 의사결정에 쓸 수 있는 데이터로 바꾸는 일입니다. PV는 페이지가 열린 횟수입니다. 이벤트는 사용자의 행동 기록이고, conversion 또는 GA4 key event는 사업 성과로 보는 이벤트입니다. UTM은 URL에 붙는 유입 표시이며, consent는 브라우저 분석을 보내도 되는지 결정하는 동의 상태입니다.
Claude Code가 유용한 이유는 측정 계획을 코드, 테스트, 문서로 빠르게 옮길 수 있기 때문입니다. Masa가 이 사이트에서 겪은 실수는 PV 성장만 보고 상품 링크 클릭, 상담 폼 완료, 무료 리소스 가입을 나누어 보지 않은 것입니다. 트래픽은 좋아 보였지만 수익 경로가 보이지 않아 이벤트 이름과 대시보드를 다시 정리해야 했습니다.
이 글은 GA4, Search Console, Cloudflare Workers Analytics Engine, Plausible, PostHog를 실무적으로 나눠 쓰는 방법을 설명합니다. 함께 보면 좋은 글은 SEO 최적화, A/B 테스트, 성능 최적화, 콘텐츠 퍼널 감사입니다.
먼저 측정 계획을 쓴다
Claude Code에는 도구 목록이 아니라 의사결정에서 출발한 표를 만들게 합니다.
이 콘텐츠 사이트의 analytics 구현 계획을 작성해 주세요.
목표는 PV 증가뿐 아니라 글 완독, CTA 클릭, 문의, 상품 클릭, 구매 경로 개선입니다.
business_question, event_name, trigger, required_params, provider, decision 열을 사용하세요.
GA4 추천 이벤트를 쓸 수 있으면 우선 사용하고, 커스텀 이벤트는 snake_case로 통일하세요.
| business_question | event_name | trigger | required_params | provider | decision |
|---|---|---|---|---|---|
| 글을 끝까지 읽는가 | article_read_complete | 글 하단이 70% 보임 | slug, category, reading_time_sec | GA4/PostHog | 도입부, 제목 구조, 내부 링크 수정 |
| CTA를 누르는가 | cta_click | 상품, 교육, 무료 PDF CTA 클릭 | slug, cta_id, cta_type, target_url | GA4/Plausible/PostHog | CTA 위치와 문구 변경 |
| 문의가 완료되는가 | generate_lead | 폼 제출 성공 | form_id, lead_source, value, currency | GA4/PostHog | 폼과 제안 설명 개선 |
| 상품 링크가 구매 의도를 만드는가 | purchase_link_click | 상품 페이지 또는 Gumroad 클릭 | product_id, price, currency, slug | GA4/PostHog | 글과 상품 매칭 조정 |
| 어떤 검색어가 가치 있는가 | gsc_query_page | Search Console API가 page/query 반환 | page, query, clicks, impressions, ctr, position | GSC | 제목과 보강 섹션 결정 |
| 브라우저 태그가 누락되는가 | edge_page_view | Cloudflare Worker가 요청 수신 | path, country, status, duration_ms | Cloudflare | 차단과 속도 문제 발견 |
GA4 이벤트는 Google의 recommended events를 기준으로 합니다. generate_lead처럼 맞는 표준 이벤트는 우선 쓰고, 글 완독처럼 사이트 고유 행동만 커스텀으로 둡니다.
flowchart LR
Reader["독자"]
Consent["동의 상태"]
Browser["browser analytics.js"]
Server["GA4 Measurement Protocol"]
GSC["Search Console API"]
Edge["Cloudflare Worker"]
Dashboard["콘텐츠, 수익, 품질 대시보드"]
Reader --> Consent --> Browser
Browser --> Server
GSC --> Dashboard
Edge --> Dashboard
Browser --> Dashboard
Server --> Dashboard
이벤트 계약을 JS로 둔다
이벤트 계약은 이름, 필수 파라미터, 전송 대상을 정한 약속입니다. 코드로 두면 Claude Code가 새 이벤트를 만들 때 테스트도 같이 고치기 쉽습니다.
// 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/",
}));
}
상품 CTA와 교육 상담 CTA는 분리해야 합니다. products 클릭은 템플릿이나 자료 관심이고, training 클릭은 팀 도입 상담 관심입니다. 둘 다 button_click으로 보내면 수익 대시보드가 흐려집니다.
브라우저 이벤트 레이어
브라우저에서는 consent, UTM 저장, 파라미터 정리, GA4/Plausible/PostHog 전송을 한 곳에 모읍니다.
// 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);
}
폼은 클릭이 아니라 성공 후 generate_lead를 보냅니다. 글 완독은 본문 하단이 보인 뒤 한 번만 보냅니다. 이 두 규칙만 지켜도 PV만 보는 상태에서 벗어납니다.
GA4, GSC, Cloudflare 예제
서버에서 확정되는 문의와 구매는 GA4 Measurement Protocol로 보완하고, 배포 전에는 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 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));
}
브라우저 태그 누락은 Workers Analytics Engine과 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 });
},
};
Cloudflare에는 이메일, 이름, 문의 본문, 원시 IP를 넣지 않습니다. 집계에 필요한 slug, event_name, 국가 수준 정보, 상태 정도로 제한합니다.
활용 사례와 함정
첫째, SEO 글 개선입니다. GSC 노출은 높은데 CTR이 낮으면 제목과 description을 고칩니다. PV는 높은데 완독률이 낮으면 도입부, 제목 순서, 코드 예제가 검색 의도와 맞는지 봅니다. 둘째, 상품 수익입니다. 상품 클릭에 product_id, price, currency, slug를 넣어 글별 수익 기여를 봅니다. 셋째, 교육 상담입니다. cta_click은 관심, generate_lead는 폼 성공으로 분리합니다. 넷째, 캠페인입니다. 최초 방문 UTM을 저장해야 며칠 뒤 문의에도 유입원을 붙일 수 있습니다.
주의할 점은 이벤트 이름 흔들림, 동의 전 전송, 서버와 브라우저의 중복 전환, GSC 데이터를 완전한 로그처럼 보는 실수, 너무 많은 서드파티 스크립트로 Core Web Vitals를 악화시키는 일입니다. 대시보드는 콘텐츠 성장, 수익 퍼널, 구현 품질 세 장으로 나누는 편이 운영하기 쉽습니다. 배포 후 24시간 안에 GA4 DebugView, Realtime, Plausible Goals, PostHog Events, Cloudflare 집계를 확인합니다.
각 이벤트에는 다음 행동도 붙여야 합니다. article_read_complete가 낮으면 제목보다 첫 세 단락과 첫 코드 예제를 먼저 고칩니다. purchase_link_click은 높은데 구매가 낮으면 글이 아니라 상품 페이지의 첫 화면, 가격 설명, 비교표를 봅니다. cta_click은 높은데 generate_lead가 낮으면 폼 항목 수, 모바일 레이아웃, 상담 기대치를 먼저 줄입니다.
GSC는 검색 수요를 보는 도구이지 완전한 로그가 아닙니다. 하루치 query로 결론을 내리지 말고 2주에서 4주 추세를 봅니다. Cloudflare edge 집계는 브라우저 태그가 얼마나 빠졌는지 보는 보조선입니다. GA4 동의 후 행동과 같은 의미로 섞으면 안 됩니다.
기존 설정을 정리하려면 products의 템플릿으로 개인 작업을 시작하거나, 팀 도입은 training and consultation으로 측정 계획과 구현 리뷰를 묶는 방식이 현실적입니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code 권한 세이프티 래더: 통제력을 잃지 않고 allow 넓히기
read-only에서 제한 편집, 검증 명령, deploy 확인까지 권한을 단계적으로 넓히는 방법.
Claude Code Small PR Proof Pack: 작은 PR을 리뷰 가능한 상태로 만드는 증거 세트
Claude Code의 작은 PR에 diff, 검증, 공개 URL, CTA 경로, rollback을 붙이는 실무 체크리스트.
Claude Code 커밋 전 리뷰 게이트: diff, 테스트, 공개 URL, CTA 확인
Claude Code 작업을 커밋하기 전에 diff 범위, build, 공개 URL, Gumroad 링크, 상담 CTA, 테스트 누락과 무관한 파일을 확인하는 방법입니다.