product-analytics について
プロダクトやエンジニアリングチーム向けに、プロダクト分析の設計と実装を支援するスキルです。イベント設計、同意管理、ファネル・コホート分析、実験を扱います。
主な機能
- 01型付きイベント分類と命名
- 02同意管理に対応した追跡パターン
- 03ユーザー識別とエイリアス統合
- 04ファネル・コホート分析
- 05機能フラグによる A/B テスト
活用シーン
- 01新製品のイベント追跡を計画
- 02不統一な分析イベントを再設計
- 03追跡前の同意管理を実装
- 04登録や決済の離脱を測定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill product-analyticsインストール後、Claude Code で/product-analyticsを入力して読み込まれたか確認します。
詳細
プロダクト分析
概要
このスキルでは、ユーザー行動に関する実用的なインサイトを提供するプロダクト分析システムの設計と実装を扱います。イベント分類の設計、主要プラットフォーム(PostHog、Amplitude、Mixpanel、Segment)向けの分析 SDK の統合、ファネル分析とコホート分析、A/B テストとフィーチャーフラグ、ユーザージャーニーのマッピング、GDPR に準拠した同意管理、カスタム指標の定義について説明します。
新しいプロダクトに分析機能を追加する場合、イベントトラッキングシステムを再設計する場合、A/B テスト基盤を構築する場合、同意管理を実装する場合、またはプロダクトチーム向けにカスタムダッシュボードを作成する場合に、このスキルを活用してください。
基本原則
- コードを書く前に分類を設計する - 適切に設計されたイベントの命名規則とプロパティのスキーマによって、分析で最もよくある失敗である、一貫性がなくクエリできないデータを防げます。まず命名規則について合意してください。
- ページではなくアクションをトラッキングする - ページビューからわかるのは、ユーザーがどこに移動したかです。コンテキストを含むイベントからは、ユーザーが何をしたか、なぜそうしたかがわかります。
project_created、file_uploaded、subscription_upgradedのようなユーザーアクションに注目してください。 - トラッキングの前にユーザーを識別する - すべてのイベントにユーザーIDが必要です。サインアップ前の匿名イベントは、ユーザーがログインしたときに認証済みIDに紐付ける(エイリアス化/統合する)必要があります。
- 同意は必須です - GDPR と CCPA では、トラッキングを開始する前にユーザーの明示的な同意を得ることが求められます。同意管理は後付けではなく、分析レイヤーに組み込んでください。
- 少ないほど効果的です - すべてをトラッキングするとノイズが増えます。具体的なプロダクト上の疑問に答えるイベントをトラッキングしてください。イベントは後から追加できますが、既存データからノイズを取り除くのははるかに困難です。
主なパターン
パターン 1:イベント分類の設計
使用する場面: 分析トラッキングを実装する前に使用します。これは、その後のすべての基盤となります。
実装:
// Event taxonomy schema
// Convention: object_action (noun_verb in past tense)
// Core event types
type AnalyticsEvent =
// Authentication
| { event: "user_signed_up"; properties: { method: "email" | "google" | "github"; referralSource?: string } }
| { event: "user_logged_in"; properties: { method: "email" | "google" | "github" } }
| { event: "user_logged_out"; properties: Record<string, never> }
// Onboarding
| { event: "onboarding_started"; properties: { variant?: string } }
| { event: "onboarding_step_completed"; properties: { step: number; stepName: string } }
| { event: "onboarding_completed"; properties: { durationSeconds: number } }
| { event: "onboarding_skipped"; properties: { lastStep: number } }
// Core product actions
| { event: "project_created"; properties: { template?: string; source: "dashboard" | "onboarding" | "api" } }
| { event: "project_deleted"; properties: { projectAge: number; itemCount: number } }
| { event: "file_uploaded"; properties: { fileType: string; fileSizeBytes: number; source: "drag_drop" | "file_picker" | "api" } }
// Subscription
| { event: "subscription_started"; properties: { plan: string; billingCycle: "monthly" | "annual"; amount: number } }
| { event: "subscription_upgraded"; properties: { fromPlan: string; toPlan: string } }
| { event: "subscription_cancelled"; properties: { reason?: string; plan: string; tenureDays: number } }
// Feature engagement
| { event: "feature_used"; properties: { feature: string; context: string } }
| { event: "search_performed"; properties: { query: string; resultCount: number; source: string } }
| { event: "export_completed"; properties: { format: "csv" | "pdf" | "json"; itemCount: number } };
// Type-safe tracking function
function track<T extends AnalyticsEvent>(
event: T["event"],
properties: T["properties"]
): void {
// Implementation below
}
// Usage - fully typed, autocomplete works
track("project_created", {
template: "blank",
source: "dashboard",
});
理由: 型付きのイベント分類によって、タイプミス(user_signedup と user_signed_up の違いなど)を防ぎ、必須プロパティが常に含まれるようにし、トラッキング計画を自己文書化できます。object_action という規則に従うことで、分析ダッシュボード上で関連イベントをグループ化できます。
パターン 2:同意管理を備えた分析クライアント
使用する場面: ユーザー行動をトラッキングするすべてのプロダクトで使用します。つまり、すべてのプロダクトで必要です。
実装:
// analytics.ts - Unified analytics client
import posthog from "posthog-js";
type ConsentStatus = "granted" | "denied" | "pending";
interface AnalyticsConfig {
posthogKey: string;
posthogHost?: string;
}
class Analytics {
private initialized = false;
private consent: ConsentStatus = "pending";
private queuedEvents: Array<{ event: string; properties: Record<string, unknown> }> = [];
init(config: AnalyticsConfig) {
posthog.init(config.posthogKey, {
api_host: config.posthogHost ?? "https://us.i.posthog.com",
persistence: "localStorage+cookie",
autocapture: false, // Explicit tracking only
capture_pageview: false, // Manual pageview tracking
capture_pageleave: true,
// Respect Do Not Track
respect_dnt: true,
// Cookie-less mode until consent
persistence: this.consent === "granted" ? "localStorage+cookie" : "memory",
});
this.initialized = true;
}
setConsent(status: ConsentStatus) {
this.consent = status;
if (status === "granted") {
posthog.opt_in_capturing();
// Flush queued events
for (const event of this.queuedEvents) {
this.trackInternal(event.event, event.properties);
}
this.queuedEvents = [];
} else if (status === "denied") {
posthog.opt_out_capturing();
this.queuedEvents = [];
}
}
identify(userId: string, traits?: Record<string, unknown>) {
if (this.consent !== "granted") return;
posthog.identify(userId, traits);
}
// Alias anonymous ID to authenticated ID (for signup flow)
alias(newId: string) {
if (this.consent !== "granted") return;
posthog.alias(newId);
}
track(event: string, properties?: Record<string, unknown>) {
if (this.consent === "denied") return;
const enrichedProperties = {
...properties,
timestamp: new Date().toISOString(),
url: typeof window !== "undefined" ? window.location.href : undefined,
referrer: typeof document !== "undefined" ? document.referrer : undefined,
};
if (this.consent === "pending") {
this.queuedEvents.push({ event, properties: enrichedProperties });
return;
}
this.trackInternal(event, enrichedProperties);
}
private trackInternal(event: string, properties: Record<string, unknown>) {
if (!this.initialized) return;
posthog.capture(event, properties);
}
page(name?: string, properties?: Record<string, unknown>) {
this.track("$pageview", { pageName: name, ...properties });
}
reset() {
posthog.reset();
}
}
export const analytics = new Analytics();
// React consent banner component
function ConsentBanner() {
const [showBanner, setShowBanner] = useState(() => {
return localStorage.getItem("analytics_consent") === null;
});
const handleConsent = (granted: boolean) => {
const status = granted ? "granted" : "denied";
localStorage.setItem("analytics_consent", status);
analytics.setConsent(status);
setShowBanner(false);
};
if (!showBanner) return null;
return (
<div role="dialog" aria-label="Cookie consent" className="consent-banner">
<p>We use analytics to improve our product. No personal data is sold.</p>
<div className="consent-actions">
<button onClick={() => handleConsent(false)}>Decline</button>
<button onClick={() => handleConsent(true)}>Accept</button>
</div>
</div>
);
}
理由: 同意を最優先する分析は、法的に求められる(GDPR、CCPA)だけでなく、ユーザーの信頼にもつながります。キュー方式にすることで、アクションの実行後にユーザーが同意した場合でもイベントが失われません。明示的なトラッキング(自動取得なし)によって、データをクリーンに保ち、意図したものだけを記録できます。
パターン 3:ファネル分析の実装
使用する場面: 複数ステップのフロー(サインアップ、オンボーディング、チェックアウト)のコンバージョンを測定する場合に使用します。
実装:
// Track each funnel step explicitly
const ONBOARDING_FUNNEL = [
"onboarding_started",
"onboarding_step_completed:profile",
"onboarding_step_completed:workspace",
"onboarding_step_completed:invite",
"onboarding_completed",
] as const;
// Instrument each step
function OnboardingFlow() {
const [currentStep, setCurrentStep] = useState(0);
useEffect(() => {
analytics.track("onboarding_started", { variant: "v2" });
}, []);
const completeStep = (stepName: string) => {
analytics.track("onboarding_step_completed", {
step: currentStep,
stepName,
timeOnStepSeconds: getTimeOnStep(),
});
setCurrentStep((prev) => prev + 1);
};
const skip = () => {
analytics.track("onboarding_skipped", {
lastStep: currentStep,
lastStepName: steps[currentStep].name,
});
router.push("/dashboard");
};
const complete = () => {
analytics.track("onboarding_completed", {
durationSeconds: getTotalDuration(),
stepsCompleted: currentStep + 1,
stepsSkipped: steps.length - currentStep - 1,
});
router.push("/dashboard");
};
// ...render steps
}
// Checkout funnel tracking
function useCheckoutFunnel() {
const trackStep = (step: string, properties?: Record<string, unknown>) => {
analytics.track(`checkout_${step}`, {
...properties,
cartValue: getCartTotal(),
itemCount: getCartItemCount(),
timestamp: Date.now(),
});
};
return {
viewCart: () => trackStep("cart_viewed"),
startCheckout: () => trackStep("started"),
addShipping: (method: string) => trackStep("shipping_added", { method }),
addPayment: (type: string) => trackStep("payment_added", { type }),
complete: (orderId: string) => trackStep("completed", { orderId }),
abandon: (step: string) => trackStep("abandoned", { lastStep: step }),
};
}
理由: ファネル分析によって、ユーザーがどの段階で離脱するかがわかります。各ステップをコンテキスト(所要時間、バリエーション、カートの金額など)とともにトラッキングすると、セグメント別の分析が可能になります。「Google 広告経由のユーザーは、オーガニック流入のユーザーより配送ステップでの離脱率が 3 倍高い」といったインサイトは、的を絞った改善につながります。
パターン 4:フィーチャーフラグを活用した A/B テスト
使用する場面: すべてのユーザーに展開する前に、統計的な厳密さをもってプロダクトの変更を検証する場合に使用します。
実装:
// PostHog feature flags for A/B testing
import posthog from "posthog-js";
// Check feature flag with typed variants
function useExperiment<T extends string>(
flagKey: string,
variants: T[]
): T | "control" | undefined {
const [variant, setVariant] = useState<T | "control" | undefined>();
useEffect(() => {
posthog.onFeatureFlags(() => {
const value = posthog.getFeatureFlag(flagKey);
if (value === false || value === undefined) {
setVariant("control");
} else if (typeof value === "string" && variants.includes(value as T)) {
setVariant(value as T);
} else {
setVariant("control");
}
// Track experiment exposure (for accurate analysis)
analytics.track("$experiment_started", {
experiment: flagKey,
variant: value ?? "control",
});
});
}, [flagKey]);
return variant;
}
// Usage in component
function PricingPage() {
const variant = useExperiment("pricing-page-redesign", ["new-layout", "social-proof"]);
if (!variant) return <LoadingSkeleton />;
switch (variant) {
case "new-layout":
return <PricingNewLayout />;
case "social-proof":
return <PricingSocialProof />;
case "control":
default:
return <PricingControl />;
}
}
理由: A/B テストによって、プロダクトに関する意思決定から主観を取り除けます。フィーチャーフラグを使うと、段階的な展開、即時のロールバック、セグメントごとのターゲティングが可能です。露出イベントをコンバージョンイベントとは別にトラッキングすることで、正確な統計分析(ITT 分析)が可能になります。
イベント命名規則のリファレンス
| 規則 | 例 | 使用する場面 |
|---|---|---|
object_action | project_created、file_uploaded | 標準的なプロダクトイベント |
$system_event | $pageview、$identify | システム/プラットフォームイベント |
experiment_* | $experiment_started | A/B テストのトラッキング |
feature_used | feature_used { feature: "export" } | 汎用的な機能の利用状況 |
アンチパターン
| アンチパターン | 問題点 | より良い方法 |
|---|---|---|
| すべてを自動で捕捉する | データがノイズだらけになり、クエリが難しく、プライバシー上のリスクがあります | 分類体系を定義したうえで、イベントを明示的に追跡します |
| イベント名に一貫性がない | userSignedUp、user_signed_up、signup のように表記が異なります | TypeScript の型を使って命名規則を徹底します |
| 同意なしに追跡する | GDPR/CCPA 違反となり、収益の最大 4% の罰金が科される可能性があります | キューパターンを使った同意バナーを設置します |
| ユーザーを識別しない | コホート分析やリテンション分析ができません | ログイン時にユーザーを識別し、登録時にエイリアスを設定します |
| イベントプロパティで PII を追跡する | プライバシー侵害やデータ保持に関する問題が発生します | イベントプロパティではメールアドレスや氏名ではなく、ユーザー ID を使います |
| すべての操作を巨大な「user_action」イベントにまとめる | 意味のあるファネルを作成できません | 意味のある操作ごとに個別のイベントを設定します |
| 悪い結果を追跡しない | うまくいったことしか分からず、失敗したことが分かりません | エラー、離脱、連打クリックを追跡します |
チェックリスト
- イベント分類体系を文書化し、型を定義している(TypeScript インターフェース)
- 命名規則(
object_actionパターン)を徹底している - 同意管理機能を備えた分析クライアントを初期化している
- ログイン時にユーザーを識別し、登録時にエイリアスを設定している
- ファネルのステップを、コンテキストプロパティとともに追跡している
- A/B テスト用にフィーチャーフラグを設定している
- 実験への曝露イベントをコンバージョンとは別に追跡している
- キューパターンを使った GDPR 同意バナーを実装している
- イベントプロパティに PII(メールアドレス、氏名、IP アドレス)を含めていない
- 主要なファネルを分析ダッシュボードで可視化している
- リテンションコホート(1 日目、7 日目、30 日目)を設定している
- 開発環境で分析イベントを検証している(デバッグモード)
関連リソース
- スキル:
growth-engineering(A/B テストのインフラストラクチャ)、authentication-patterns(識別/エイリアス設定のフロー) - スキル:
accessibility-a11y(アクセシブルな同意バナー) - ルール:
docs/reference/stacks/react-typescript.md(分析用 React フック)