PostHog Analytics 技能
使用基础.md + [框架].md 加载
实现 PostHog 产品分析 - 事件跟踪、用户识别、功能标志和项目特定仪表板。
来源: PostHog 文档 | 产品分析 | 功能标志
理念
衡量重要之事,而非一切。
分析应该回答具体问题:
- 用户是否获得价值?(激活、留存)
- 用户在哪里挣扎?(漏斗、流失点)
- 哪些功能推动参与度?(功能使用)
- 产品是否在增长?(获取、推荐)
不要追踪一切。追踪那些能够指导决策的信息。
安装
Next.js (App Router)
npm install posthog-js
// lib/posthog.ts
import posthog from 'posthog-js';
export function initPostHog() {
if (typeof window !== 'undefined' && !posthog.__loaded) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
person_profiles: 'identified_only', // 仅为已识别用户创建档案
capture_pageview: false, // 我们将手动处理 SPA 的这个功能
capture_pageleave: true,
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') {
posthog.debug();
}
},
});
}
return posthog;
}
export { posthog };
// app/providers.tsx
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { initPostHog, posthog } from '@/lib/posthog';
export function PostHogProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
initPostHog();
}, []);
// 跟踪页面浏览
useEffect(() => {
if (pathname) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url += `?${searchParams.toString()}`;
}
posthog.capture('$pageview', { $current_url: url });
}
}, [pathname, searchParams]);
return <>{children}</>;
}
// app/layout.tsx
import { PostHogProvider } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh">
<body>
<PostHogProvider>
{children}
</PostHogProvider>
</body>
</html>
);
}
React (Vite/CRA)
// src/posthog.ts
import posthog from 'posthog-js';
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
api_host: import.meta.env.VITE_POSTHOG_HOST || 'https://us.i.posthog.com',
person_profiles: 'identified_only',
});
export { posthog };
// src/main.tsx
import { PostHogProvider } from 'posthog-js/react';
import { posthog } from './posthog';
ReactDOM.createRoot(document.getElementById('root')!).render(
<PostHogProvider client={posthog}>
<App />
</PostHogProvider>
);
Python (FastAPI/Flask)
pip install posthog
# analytics/posthog_client.py
import posthog
from functools import lru_cache
@lru_cache()
def get_posthog():
posthog.project_api_key = os.environ["POSTHOG_API_KEY"]
posthog.host = os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com")
posthog.debug = os.environ.get("ENV") == "development"
return posthog
# 使用方法
def track_event(user_id: str, event: str, properties: dict = None):
ph = get_posthog()
ph.capture(
distinct_id=user_id,
event=event,
properties=properties or {}
)
def identify_user(user_id: str, properties: dict):
ph = get_posthog()
ph.identify(user_id, properties)
Node.js (Express/Hono)
npm install posthog-node
// lib/posthog.ts
import { PostHog } from 'posthog-node';
const posthog = new PostHog(process.env.POSTHog_API_KEY!, {
host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
});
// 终止时刷新
process.on('SIGTERM', () => posthog.shutdown());
export { posthog };
// 使用方法
export function trackEvent(userId: string, event: string, properties?: Record<string, any>) {
posthog.capture({
distinctId: userId,
event,
properties,
});
}
export function identifyUser(userId: string, properties: Record<string, any>) {
posthog.identify({
distinctId: userId,
properties,
});
}
环境变量
# .env.local (Next.js) - 安全:这些应该是公开的
NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
# .env (后端) - 保密
POSTHOG_API_KEY=phc_xxxxxxxxxxxxxxxxxxxx
POSTHOG_HOST=https://us.i.posthog.com
添加到 credentials.md 模式:
'POSTHOG_API_KEY': r'phc_[A-Za-z0-9]+',
用户识别
何时识别
// 注册时识别
async function handleSignup(email: string, name: string) {
const user = await createUser(email, name);
posthog.identify(user.id, {
email: user.email,
name: user.name,
created_at: user.createdAt,
plan: 'free',
});
posthog.capture('user_signed_up', {
signup_method: 'email',
});
}
// 登录时识别
async function handleLogin(email: string) {
const user = await authenticateUser(email);
posthog.identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan,
last_login: new Date().toISOString(),
});
posthog.capture('user_logged_in');
}
// 注销时重置
function handleLogout() {
posthog.capture('user_logged_out');
posthog.reset(); // 清除身份
}
用户属性
// 跟踪的标准属性
interface UserProperties {
// 身份
email: string;
name: string;
// 生命周期
created_at: string;
plan: 'free' | 'pro' | 'enterprise';
// 参与度
onboarding_completed: boolean;
feature_count: number;
// 商业
company_name?: string;
company_size?: string;
industry?: string;
}
// 当属性变化时更新属性
posthog.capture('$set', {
$set: { plan: 'pro' },
});
事件跟踪模式
事件命名约定
// 格式:[对象]_[动作]
// 使用 snake_case,过去时用于动作
// ✅ 好的事件名称
'user_signed_up'
'feature_created'
'subscription_upgraded'
'onboarding_completed'
'invite_sent'
'file_uploaded'
'search_performed'
'checkout_started'
'payment_completed'
// ❌ 坏的事件名称
'click' // 太模糊
'ButtonClick' // 不是 snake_case
'user signup' // 有空格
'creatingFeature' // 不是过去时
按类别划分的核心事件
// === 认证 ===
posthog.capture('user_signed_up', {
signup_method: 'google' | 'email' | 'github',
referral_source: 'organic' | 'paid' | 'referral',
});
posthog.capture('user_logged_in', {
login_method: 'google' | 'email' | 'magic_link',
});
posthog.capture('user_logged_out');
posthog.capture('password_reset_requested');
// === 引导 ===
posthog.capture('onboarding_started');
posthog.capture('onboarding_step_completed', {
step_name: 'profile' | 'preferences' | 'first_action',
step_number: 1,
total_steps: 3,
});
posthog.capture('onboarding_completed', {
duration_seconds: 120,
steps_skipped: 0,
});
posthog.capture('onboarding_skipped', {
skipped_at_step: 2,
});
// === 功能使用 ===
posthog.capture('feature_used', {
feature_name: 'export' | 'share' | 'duplicate',
context: 'dashboard' | 'editor',
});
posthog.capture('[resource]_created', {
resource_type: 'project' | 'document' | 'team',
// 特定于资源的属性
});
posthog.capture('[resource]_updated', {
resource_type: 'project',
fields_changed: ['name', 'description'],
});
posthog.capture('[resource]_deleted', {
resource_type: 'project',
});
// === 账单 ===
posthog.capture('pricing_page_viewed', {
current_plan: 'free',
});
posthog.capture('checkout_started', {
plan: 'pro',
billing_period: 'monthly' | 'annual',
price: 29,
});
posthog.capture('subscription_upgraded', {
from_plan: 'free',
to_plan: 'pro',
mrr_change: 29,
});
posthog.capture('subscription_downgraded', {
from_plan: 'pro',
to_plan: 'free',
reason: 'too_expensive' | 'missing_features' | 'not_using',
});
posthog.capture('subscription_cancelled', {
plan: 'pro',
reason: 'string',
feedback: 'string',
});
// === 错误 ===
posthog.capture('error_occurred', {
error_type: 'api_error' | 'validation_error' | 'network_error',
error_message: 'string',
error_code: 'string',
page: '/dashboard',
});
React 钩子用于跟踪
// hooks/useTrack.ts
import { useCallback } from 'react';
import { posthog } from '@/lib/posthog';
export function useTrack() {
const track = useCallback((event: string, properties?: Record<string, any>) => {
posthog.capture(event, {
...properties,
timestamp: new Date().toISOString(),
});
}, []);
return { track };
}
// 使用方法
function CreateProjectButton() {
const { track } = useTrack();
const handleCreate = async () => {
track('project_creation_started');
try {
const project = await createProject();
track('project_created', {
project_id: project.id,
template_used: project.template,
});
} catch (error) {
track('project_creation_failed', {
error_message: error.message,
});
}
};
return <button onClick={handleCreate}>Create Project</button>;
}
功能标志
设置
// 检查功能标志(客户端)
import { useFeatureFlagEnabled } from 'posthog-js/react';
function NewFeature() {
const showNewUI = useFeatureFlagEnabled('new-dashboard-ui');
if (showNewUI) {
return <NewDashboard />;
}
return <OldDashboard />;
}
// 带载荷
import { useFeatureFlagPayload } from 'posthog-js/react';
function PricingPage() {
const pricingConfig = useFeatureFlagPayload('pricing-experiment');
// pricingConfig = { price: 29, showAnnual: true }
return <Pricing config={pricingConfig} />;
}
服务器端(Next.js)
// app/dashboard/page.tsx
import { PostHog } from 'posthog-node';
import { cookies } from 'next/headers';
async function getFeatureFlags(userId: string) {
const posthog = new PostHog(process.env.POSTHOG_API_KEY!);
const flags = await posthog.getAllFlags(userId);
await posthog.shutdown();
return flags;
}
export default async function Dashboard() {
const cookieStore = cookies();
const userId = cookieStore.get('user_id')?.value;
const flags = await getFeatureFlags(userId);
return (
<div>
{flags['new-dashboard'] && <NewFeature />}
</div>
);
}
A/B 测试
// 跟踪实验曝光
function ExperimentComponent() {
const variant = useFeatureFlagEnabled('checkout-experiment');
useEffect(() => {
posthog.capture('experiment_viewed', {
experiment: 'checkout-experiment',
variant: variant ? 'test' : 'control',
});
}, [variant]);
return variant ? <NewCheckout /> : <OldCheckout />;
}
项目特定仪表板
SaaS 产品
## SaaS 仪表板必备
### 1. 获取仪表板
**问题解答:** 用户从何而来?什么转化?
创建见解:
- [ ] 按来源注册(每日/每周趋势)
- [ ] 注册转化率按着陆页
- [ ] 从首次访问到注册的时间
- [ ] 注册漏斗:访问 → 注册页面 → 表单开始 → 完成
### 2. 激活仪表板
**问题解答:** 新用户是否获得价值?
创建见解:
- [ ] 引导完成率
- [ ] 首次关键动作的时间
- [ ] 激活率(% 在前 7 天内达到 "aha 时刻")
- [ ] 按引导步骤的流失
- [ ] 第一次会话中的功能采用
### 3. 参与仪表板
**问题解答:** 用户如何使用产品?
创建见解:
- [ ] DAU/WAU/MAU 趋势
- [ ] 功能使用热图
- [ ] 会话持续时间分布
- [ ] 每次会话的动作
- [ ] 强力用户与普通用户
### 4. 留存仪表板
**问题解答:** 用户是否回来?
创建见解:
- [ ] 留存队列(D1, D7, D30)
- [ ] 按计划的流失率
- [ ] 重新激活率
- [ ] 流失前最后的动作
- [ ] 与留存相关的功能
### 5. 收入仪表板
**问题解答:** 业务是否在增长?
创建见解:
- [ ] MRR 趋势
- [ ] 升级与降级
- [ ] 试用到付费转化
- [ ] 按计划的收入
- [ ] 按获取来源的 LTV
电子商务
## 电子商务仪表板必备
### 1. 转化漏斗
创建见解:
- [ ] 全漏斗:浏览 → PDP → 添加到购物车 → 结账 → 购买
- [ ] 购物车放弃率
- [ ] 结账放弃率按步骤
- [ ] 支付失败率
### 2. 产品性能
创建见解:
- [ ] 按产品查看 → 购买(按产品)
- [ ] 按类别添加到购物车率
- [ ] 搜索 → 购买相关性
- [ ] 交叉销售效果
### 3. 客户仪表板
创建见解:
- [ ] 重复购买率
- [ ] 平均订单价值趋势
- [ ] 客户终身价值
- [ ] 购买频率分布
内容/媒体
## 内容仪表板必备
### 1. 消费仪表板
创建见解:
- [ ] 按类型查看内容
- [ ] 阅读/观看完成率
- [ ] 内容上的时间
- [ ] 滚动深度分布
### 2. 参与仪表板
创建见解:
- [ ] 按内容分享
- [ ] 每篇文章的评论
- [ ] 保存/书签率
- [ ] 回访相同内容
### 3. 增长仪表板
创建见解:
- [ ] 新访客与回访者
- [ ] 电子邮件注册率
- [ ] 引荐流量来源
AI/LLM 应用
## AI 应用仪表板必备
### 1. 使用仪表板
创建见解:
- [ ] 用户每天查询次数
- [ ] 令牌使用分布
- [ ] 响应时间 p50/p95
- [ ] 按查询类型的错误率
### 2. 质量仪表板
创建见解:
- [ ] 用户反馈(点赞/点踩)
- [ ] 再生率(用户要求新响应)
- [ ] 编辑率(用户修改 AI 输出)
- [ ] 后续查询率
### 3. 成本仪表板
创建见解:
- [ ] 用户令牌成本
- [ ] 按模型成本
- [ ] 按功能成本
- [ ] 效率趋势(价值/成本)
创建仪表板
使用 PostHog MCP
当为项目设置分析时:
1. 首先,检查现有仪表板:
- 使用 `dashboards-get-all` 列出当前仪表板
2. 创建适合项目的仪表板:
- 使用 `dashboard-create` 创建具有描述性的名称
3. 为每个仪表板创建见解:
- 使用 `query-run` 测试查询
- 使用 `insight-create-from-query` 保存
- 使用 `add-insight-to-dashboard` 组织
4. 设置关键漏斗:
- 注册漏斗
- 引导漏斗
- 购买/转化漏斗
仪表板创建工作流程
// 示例:通过 MCP 创建 SaaS 仪表板
// 1. 创建仪表板
const dashboard = await mcp_posthog_dashboard_create({
name: "激活指标",
description: "跟踪新用户激活和引导",
tags: ["saas", "activation"],
});
// 2. 创建见解
const signupFunnel = await mcp_posthog_query_run({
query: {
kind: "InsightVizNode",
source: {
kind: "FunnelsQuery",
series: [
{ kind: "EventsNode", event: "user_signed_up", name: "已注册" },
{ kind: "EventsNode", event: "onboarding_started", name: "开始引导" },
{ kind: "EventsNode", event: "onboarding_completed", name: "完成引导" },
{ kind: "EventsNode", event: "first_value_action", name: "首次价值" },
],
dateRange: { date_from: "-30d" },
},
},
});
// 3. 保存并添加到仪表板
const insight = await mcp_posthog_insight_create_from_query({
name: "注册到激活漏斗",
query: signupFunnel.query,
favorited: true,
});
await mcp_posthog_add_insight_to_dashboard({
insightId: insight.id,
dashboardId: dashboard.id,
});
隐私与合规
GDPR 合规
// 处理退出选项
export function handleCookieConsent(consent: boolean) {
if (consent) {
posthog.opt_in_capturing();
} else {
posthog.opt_out_capturing();
}
}
// 检查同意状态
const hasConsent = posthog.has_opted_in_capturing();
// 初始化时检查同意
posthog.init(key, {
opt_out_capturing_by_default: true, // 需要明确的同意
respect_dnt: true, // 尊重不追踪
});
永不跟踪的数据
// ❌ 永不跟踪这些
posthog.capture('event', {
password: '...', // 凭据
credit_card: '...', // 支付信息
ssn: '...', // 政府 ID
medical_info: '...', // 健康数据
full_address: '...', // 详细位置
});
// ✅ 可以跟踪
posthog.capture('event', {
country: 'US', // 一般位置
plan: 'pro', // 产品信息
feature_used: 'export', // 使用情况
});
属性清理
// lib/analytics.ts
const SENSITIVE_KEYS = ['password', 'token', 'secret', 'credit', 'ssn'];
function sanitizeProperties(props: Record<string, any>): Record<string, any> {
return Object.fromEntries(
Object.entries(props).filter(([key]) =>
!SENSITIVE_KEYS.some(sensitive => key.toLowerCase().includes(sensitive))
)
);
}
export function safeCapture(event: string, properties?: Record<string, any>) {
posthog.capture(event, sanitizeProperties(properties || {}));
}
测试分析
开发模式
// 在开发中禁用
if (process.env.NODE_ENV === 'development') {
posthog.opt_out_capturing();
// 或使用调试模式
posthog.debug();
}
E2E 测试
// playwright/fixtures.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
page: async ({ page }, use) => {
// 模拟 PostHog 捕获事件
await page.addInitScript(() => {
window.capturedEvents = [];
window.posthog = {
capture: (event, props) => {
window.capturedEvents.push({ event, props });
},
identify: () => {},
reset: () => {},
};
});
await use(page);
},
});
// 在测试中
test('跟踪注册事件', async ({ page }) => {
await page.goto('/signup');
await page.fill('[name=email]', 'test@example.com');
await page.click('button[type=submit]');
const events = await page.evaluate(() => window.capturedEvents);
expect(events).toContainEqual({
event: 'user_signed_up',
props: expect.objectContaining({ signup_method: 'email' }),
});
});
调试
PostHog 工具栏
// 启用工具栏进行调试
posthog.init(key, {
// ...
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') {
posthog.debug();
// 工具栏可通过 PostHog 仪表板获得
}
},
});
事件调试
// 在开发中记录所有事件
posthog.init(key, {
_onCapture: (eventName, eventData) => {
if (process.env.NODE_ENV === 'development') {
console.log('PostHog 事件:', eventName, eventData);
}
},
});
快速参考
用户生命周期事件清单
## 必须跟踪的事件
### 获取
- [ ] `page_viewed`(自动捕获页面浏览)
- [ ] `user_signed_up`
- [ ] `user_logged_in`
### 激活
- [ ] `onboarding_started`
- [ ] `onboarding_step_completed`
- [ ] `onboarding_completed`
- [ ] `first_[key_action]`(你的 "aha 时刻")
### 参与度
- [ ] `[feature]_used`
- [ ] `[resource]_created`
- [ ] `search_performed`
- [ ] `invite_sent`
### 收入
- [ ] `pricing_page_viewed`
- [ ] `checkout_started`
- [ ] `subscription_upgraded`
- [ ] `subscription_cancelled`
### 留存
- [ ] `session_started`
- [ ] `feature_[x]_used`(强力功能)
仪表板模板
| 项目类型 | 关键仪表板 |
|---|---|
| SaaS | 获取、激活、参与度、留存、收入 |
| 电子商务 | 转化漏斗、产品性能、客户 LTV |
| 内容 | 消费、参与度、增长 |
| AI/LLM | 使用、质量、成本 |
| 移动应用 | 安装、引导、DAU/MAU、崩溃 |
始终包含的属性
// PostHog 自动丰富
$current_url
$browser
$device_type
$os
// 您自己添加这些
user_plan // 'free' | 'pro' | 'enterprise'
user_role // 'admin' | 'member'
company_id // 对于 B2B
feature_context // 应用中的位置