Tips & Tricks (更新: 2026/6/3)

Claude Codeでアナリティクス実装: GA4・GSC・CloudflareでPVと収益を測る

Claude CodeでGA4/GSC/Cloudflare計測を設計し、PV・CTA・収益導線まで検証する実装ガイド。

Claude Codeでアナリティクス実装: GA4・GSC・CloudflareでPVと収益を測る

アナリティクス実装は「タグを貼る作業」ではない

アナリティクス実装とは、PV、クリック、読了、問い合わせ、購入導線を、改善に使えるデータへ変える作業です。PVはページビュー、つまりページが表示された回数です。イベントは「CTAを押した」「フォーム送信に成功した」のような行動の記録です。コンバージョン、またはGA4のKey eventは、事業上の成果として扱うイベントです。UTMは広告やSNSの流入元をURLに残す印で、同意管理は計測してよい状態かを確認する仕組みです。

Claude Codeを使う価値は、計測設計をコード、テスト、レビュー表へ一気に落とし込める点にあります。Masaがこのサイトで失敗したのは、PVだけを見て「読まれている」と判断し、相談フォームの完了、無料PDFの登録、商品リンクのクリックを別々に測っていなかったことでした。PVは伸びているのに売上や相談につながらない理由を説明できず、あとからイベント名とダッシュボードを作り直しました。

この記事では、Claude Codeに実装を任せる前提で、GA4、Search Console、Cloudflare Workers Analytics Engine、Plausible、PostHogをどう使い分けるかを整理します。SEO改善はClaude Code SEO最適化、実験設計はA/Bテスト実装、導線の棚卸しはコンテンツファネル監査も合わせて読むとつながります。

測定計画を先に固定する

最初に書くのはコードではなく測定計画です。Claude Codeには、次の表を作らせてから実装に入ります。

このサイトのアナリティクス実装計画を作ってください。
目的はPV増加だけでなく、読了、CTAクリック、問い合わせ、商品クリック、購入導線の改善です。
列は business_question, event_name, trigger, required_params, provider, decision にしてください。
GA4の推奨イベントに寄せられるものは寄せ、独自イベントは snake_case にしてください。
business_questionevent_nametriggerrequired_paramsproviderdecision
記事は最後まで読まれているかarticle_read_complete本文末尾が70%以上見えたslug, category, reading_time_secGA4/PostHog導入文、見出し、内部リンクを直す
CTAは押されているかcta_click商品・研修・無料PDFのCTAを押したslug, cta_id, cta_type, target_urlGA4/Plausible/PostHogCTA位置と文言を変える
問い合わせは完了したかgenerate_leadフォーム送信が成功したform_id, lead_source, value, currencyGA4/PostHogフォーム項目と訴求を直す
商品リンクは収益導線になっているかpurchase_link_click商品ページやGumroadリンクを押したproduct_id, price, currency, slugGA4/PostHog記事と商品の対応を見直す
検索流入の質は高いかgsc_query_pageSearch Console APIでページとクエリを取得page, query, clicks, impressions, ctr, positionGSCタイトルと追記対象を決める
エッジ側のPVは欠損していないかedge_page_viewCloudflare Workerがリクエストを受けたpath, country, status, duration_msCloudflareタグブロックや速度劣化を見つける

GA4の推奨イベントはGoogle Analyticsのrecommended eventsで確認します。generate_leadpurchaseのように標準イベントへ寄せられるものは寄せ、記事固有の読了や商品リンククリックは独自イベントとして命名します。

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で共有する

イベント契約とは、イベント名、必須パラメータ、送信先を1つにまとめた約束です。これを先に置くと、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) {
  const result = validateEvent("cta_click", {
    slug: "claude-code-analytics-implementation",
    cta_id: "products_footer",
    cta_type: "product",
    target_url: "/products/",
  });
  console.log(result);
}

/products//training/は、商品購入と相談の代表CTAとして別々に測ります。同じ「詳しく見る」ボタンでも、商品一覧へ送るクリックと研修相談へ送るクリックでは改善判断が違います。

ブラウザ側の共通イベント層

次はブラウザ側です。直接gtagを各コンポーネントから呼ばず、同意、UTM保存、パラメータ掃除、複数プロバイダー送信を1か所へ寄せます。

// 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);
}

export function trackCtaClick({ slug, ctaId, ctaType, targetUrl }) {
  trackEvent("cta_click", {
    slug,
    cta_id: ctaId,
    cta_type: ctaType,
    target_url: targetUrl,
  });
}

読了はIntersectionObserverで本文末尾が見えた時だけ送ります。フォームは送信ボタンのクリックではなく、送信成功後にgenerate_leadを送ります。クリックだけでリード扱いにすると、バリデーションエラーや通信失敗まで成果に見えてしまいます。

GA4とSearch Consoleをつなげる

購入、問い合わせ、Webhookのようにサーバーで確定する成果は、GA4 Measurement Protocolで補完します。実装時はMeasurement Protocolvalidation 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はブラウザイベントではなく、検索クエリとページの組み合わせを見るための外部データです。Search Analytics APIでクリック、表示回数、CTR、平均掲載順位を取り、GA4の読了率やCTAと並べます。

// 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));
}

GSCは全クリックの生ログではなく、Search Console側の制限を受けた集計データです。順位の低いクエリを1日単位で断定せず、2週間から4週間の傾向で見ます。

Cloudflare側でタグ欠損を補う

広告ブロッカーや同意拒否により、ブラウザタグだけではPVが欠けます。Cloudflare Workers Analytics Engineを使うと、エッジで受けたリクエストを別系統で集計できます。公式のWorkers Analytics EnginewriteDataPointの例を見ながら、個人情報を入れない形で保存します。

// 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側にはメールアドレス、氏名、自由入力の問い合わせ本文を入れません。集計に必要なslugevent_name、国コード、ステータス程度に抑えます。ブラウザ同意が必要なマーケティング計測と、サーバー側の匿名集計を混ぜて説明しないことも重要です。

PVと収益のユースケース

1つ目はSEO記事の改善です。Search Consoleで表示回数が多いのにCTRが低い記事は、タイトルとdescriptionを疑います。GA4でPVは多いのにarticle_read_completeが低い記事は、導入文が長い、結論が遅い、コード例が実務から遠い可能性があります。

2つ目は商品導線です。記事末尾から教材・テンプレート一覧へ送るCTAはpurchase_link_clickで測ります。product_idpricecurrencyを入れると、どの記事が有料テンプレートの関心を作ったかを比較できます。

3つ目は研修・相談導線です。Claude Code研修・導入相談へ送るCTAはcta_click、フォーム成功はgenerate_leadに分けます。クリックが多く問い合わせが少ないなら、フォーム項目、スマホ表示、相談内容の説明を直します。

4つ目は広告やSNSキャンペーンです。UTMを初回訪問時に保存しておけば、問い合わせ時点でURLからUTMが消えていても、generate_leadに流入元を付けられます。短期キャンペーンほどUTM名を先に固定します。

具体的な落とし穴

イベント名を途中で変えるのは痛い失敗です。lead_submitからgenerate_leadへ変えると、過去データとの比較が切れます。変更が必要なら移行期間を決め、両方を送るか、ダッシュボード側で統合します。

同意前にイベントを送るのも危険です。国や地域、広告利用の有無で要件は変わります。この記事のブラウザコードはデフォルトで送信しない設計ですが、実サイトではプライバシーポリシー、Cookieバナー、保存期間、個人情報をイベントに入れないルールを確認してください。

サーバーイベントの二重計測もよく起きます。フォーム成功時にブラウザでgenerate_leadを送り、サーバーでも同じイベントを送ると成果が倍になります。イベントごとに確定地点を1つ決めます。

最後に、計測コードで表示速度を落とさないことです。タグを増やしすぎるとLCPやINPへ影響します。Plausibleは軽量なPVとゴール確認、PostHogはファネルや録画が必要な場面、Cloudflareはエッジ集計の補助、というように役割を絞ります。

ロールアウトチェックリスト

  • 測定計画にイベント名、発火条件、必須パラメータ、担当者がある
  • GA4推奨イベントに寄せられるものは寄せた
  • Search ConsoleのクエリとページをGA4の読了・CTAと並べる
  • Cloudflare側に個人情報を保存していない
  • 同意前にブラウザイベントを送っていない
  • UTMが初回訪問時に保存される
  • フォーム完了とCTAクリックを混同していない
  • サーバーイベントの二重送信を防いでいる
  • 公開後24時間以内にGA4 DebugView、Realtime、Plausible Goals、PostHog Events、Cloudflare集計を確認する

この記事で紹介した内容を実際に試した結果

この設計でPV、読了、CTA、問い合わせ、商品クリックを分けると、記事改善の判断が速くなります。特に「検索では見られるが読まれない記事」「読まれるが商品へ進まない記事」「押されるが問い合わせに進まない記事」を分けられるため、タイトル修正、CTA位置、商品ページ、フォーム改善の優先順位が明確になります。Claude Codeに実装だけを頼むのではなく、測定計画、イベント契約、JS検証、公開後の確認まで同じ作業単位にすると、収益改善に使えるアナリティクスになります。

#Claude Code #アナリティクス #GA4 #Plausible #PostHog
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。