访问统计
配置第一方访问统计、事件采集、转化漏斗、Web Vitals、数据保留和第三方统计服务。
Saavo 内置了一套面向单个站点的第一方访问统计功能。前台脚本负责采集页面浏览、自定义事件和 Web Vitals,数据经过 Cloudflare Queue 写入独立的 D1 数据库,管理员可在 Dashboard 中查看流量、事件、会话、转化漏斗和页面性能。
项目也支持 Google Analytics、Microsoft Clarity、Umami 等第三方统计服务。第一方统计与第三方脚本彼此独立:第一方统计由 config/deploy.ts 控制,第三方脚本在 config/analytics.ts 中配置。
这套功能只服务于当前部署的站点,不是多站点统计平台。这套访问统计定位是轻量级的 SaaS 网站前期使用(前 100 天),后期可以考虑迁移到第三方统计服务。
已有功能
| 功能 | 入口或资源 | 当前默认值 |
|---|---|---|
| 浏览器采集脚本 | /js/analytics.js | 自动记录页面浏览和 SPA 路由变化 |
| 第一方采集接口 | POST /api/analytics/collect | 已开启 |
| 异步写入队列 | analytics-events / ANALYTICS_QUEUE | 已配置生产者和消费者 |
| 统计数据库 | ANALYTICS_DB | 与业务数据库 DB 分开 |
| 统计后台 | /dashboard/analytics | 仅管理员可访问 |
| 数据清理 | Cron 30 */6 * * * | 每 6 小时执行一次 |
| 第三方统计脚本 | config/analytics.ts | 所需 ID 均为空,不会加载 |
统计后台包含以下页面:
- 总览:页面浏览量、访客数、访问次数、跳出率、平均访问时长,以及页面、来源、设备和地区分布。
- 事件:自定义事件趋势、事件属性和最近活动。
- 会话:浏览器会话、访问记录、页面与事件时间线,以及
identify()写入的属性。 - 转化漏斗:按配置中的步骤和时间窗口计算进入人数、完成人数、转化率和流失情况。
- 性能:真实用户的 LCP、INP、CLS、FCP、TTFB 和页面停留时长,并提供 P75、趋势和分组统计。
数据写入流程
一次正常的浏览器采集会经过以下流程:
- 页面渲染时检查统计总开关、站点状态、访问域名和忽略路径,符合条件才加载
/js/analytics.js。 - 浏览器脚本采集页面浏览、自定义事件或性能指标,并发送到
POST /api/analytics/collect。 - 采集接口校验站点 ID、访问划分策略、来源域名、忽略规则、机器人流量和请求频率。
- 校验通过后,事件先写入
analytics-events队列,再由消费者写入ANALYTICS_DB。 - Dashboard 直接查询保留期内的统计数据,因此队列尚未消费时,后台不会立即出现刚上报的事件。
采集接口成功接收消息后返回 202。队列写入数据库失败时最多尝试 3 次,最后一次仍然失败会记录错误并放弃该消息,当前项目没有为统计队列配置死信队列。
默认配置
第一方统计配置位于 config/deploy.ts 的 analytics 段。当前主要配置如下:
analytics: {
enabled: true,
retention: {
enabled: true,
rawDays: 100,
eventBatchSize: 5_000,
sessionBatchSize: 1_000,
maxD1OperationsPerRun: 40,
maxRuntime: '2m',
},
site: {
id: '00000000-0000-4000-8000-000000000001',
status: 'active',
allowedDomains: [],
visitStrategy: 'umami',
tracker: {
performance: true,
respectDoNotTrack: true,
excludeSearch: false,
excludeHash: true,
},
collection: {
ignoredPathPrefixes: ['/dashboard'],
ignoredIps: [],
ignoredUserAgentSubstrings: [],
ignoredDistinctIds: [],
},
},
rateLimit: {
enabled: true,
maxRequests: 120,
windowDuration: '1m',
},
}这些默认值表示:
/dashboard及其子路径不会加载第一方统计脚本,也不会被采集接口接受。- 保留 URL 查询参数,但移除 URL hash。
- 采集 Web Vitals 和页面停留时长,并尊重浏览器的 Do Not Track 设置。
- 每个站点、每个可信客户端 IP 在 1 分钟内最多提交 120 次采集请求。
- 原始事件保留当前 UTC 日以及此前 100 个完整 UTC 日。
rawDays不会直接删除已识别会话及其 Trait(会话属性),清理任务只会额外回收已经没有事件引用的匿名会话。
必须修改的配置
上线前需要确认站点 ID、站点地址和 Cloudflare 资源都属于当前项目。
- 为
site.id生成一个新的 UUID。 - 确认
VITE_SITE_URL是当前环境的站点地址。统计服务会自动允许该地址的主机名,本地站点会自动允许localhost和127.0.0.1。只有需要接受额外域名时,才在allowedDomains中补充,默认保持空数组即可。 - 本地环境由
saavo:init初始化ANALYTICS_DB,首次远程部署时,由deploy:init创建并绑定数据库和analytics-eventsQueue。 - 后续数据库变更写入
schema/migrations,再通过db:migrate:local或部署脚本应用,不要把初始化文件当作增量迁移重复执行。具体流程见本地运行。 - 将
analytics.site.dashboard.funnels中的示例漏斗改为产品真实的转化路径。
当前项目预置了四个漏斗:联盟推荐转化、注册转化、价格页到创建 Checkout、登录后继续 Checkout。这些漏斗只是样板,步骤、事件名和属性过滤都来自当前模板业务,不应原样用于其他产品。
如何划分访问次数
visitStrategy 决定一次访问如何划分。当前值为 umami,也支持 sliding。站点开始产生正式数据后,不要直接修改这个值,否则同一批数据会混用不同的统计口径。确需切换时,应使用新的站点 ID,或者先完成旧数据的清理或迁移。
数据保留期
原始事件清理由 30 */6 * * * 触发。任务按批次删除过期事件和无事件引用的匿名会话,并受单次 D1 操作数和运行时间限制。一次没有清完时,后续 Cron 会继续执行。如果清理速度长期低于新增数据速度,系统会写入警告和告警日志。
wrangler.jsonc 中的 Cron 字符串必须与 src/entry/index.ts 的 ANALYTICS_RETENTION_CRON 完全一致,否则定时入口不会执行对应任务。
记录业务事件
浏览器事件
浏览器端使用 trackBrowserEvent():
import { trackBrowserEvent } from '@/libs/analytics/client';
trackBrowserEvent('login_modal_opened', {
reason: 'checkout',
});浏览器事件名可以由业务自行定义。建议集中维护命名和属性规范,避免同一行为出现多套名称。统计脚本未加载、被用户禁用或请求失败时,trackBrowserEvent() 会直接跳过,不会影响原来的业务操作。
也可以通过 HTML 属性记录点击事件:
<button
data-saavo-event="checkout_started"
data-saavo-event-plan="pro"
>
立即购买
</button>服务端事件
需要由服务端确认的业务结果使用 trackRequestEvent():
import { trackRequestEvent } from '@/core/services/analytics/service';
trackRequestEvent(c, {
name: 'checkout_session_created',
data: {
productId,
planId,
planType: plan.type,
provider: 'stripe',
},
});当前服务端只接受以下事件:
signup_completedlogin_completedcheckout_session_createdaffiliate_referral_clicked
新增服务端事件时,必须扩展 AnalyticsRequestEvent 类型并明确事件属性,不能直接传入任意字符串。服务端事件通过 waitUntil() 异步提交,统计失败只记日志,不会把已经成功的注册、登录或 Checkout 请求改成失败。
项目中的 getService() 和 postService() 会自动附带当前统计会话的签名请求头,使后续服务端事件能够沿用浏览器侧的 Session(会话)和 Visit(访问)。业务代码不要自行生成或解析 x-saavo-* 请求头。
关联会话
需要把匿名会话与内部用户标识关联时,可以使用:
window.saavo.identify('internal-user-id', {
plan: 'pro',
});请使用内部 ID 或不可逆哈希,不要上报邮箱、手机号、姓名等非必要个人信息。identify() 保存的是会话属性的最新状态,不是属性变更历史。需要保留当时状态时,应把属性写入对应事件。
排除不需要统计的流量
当前项目提供四种配置级排除条件:
ignoredPathPrefixes:按路径前缀排除,默认包含/dashboard。ignoredIps:按可信客户端 IP 精确排除。ignoredUserAgentSubstrings:按 User-Agent 子串排除,不区分大小写。ignoredDistinctIds:按浏览器提供的 distinct ID(访客标识)精确排除。
这些规则在服务端执行,修改后需要重新部署。
管理员也可以在 Dashboard 的用户菜单中选择 Exclude this browser,把 saavo.disabled=1 写入当前浏览器的 localStorage。该设置只影响这个浏览器,不会按账号或 IP 排除其他设备。恢复时选择 Resume browser analytics,或者删除该存储项。

接入第三方统计服务
config/analytics.ts 当前支持:
- Google Analytics
- Microsoft Clarity
- Cloudflare Web Analytics
- Umami
- Plausible
- PostHog
- Seline
- Ahrefs Web Analytics
- DataFast
- OpenPanel
所有提供商的必填 ID、密钥或脚本地址默认都是空值,因此不会加载任何第三方统计脚本。使用某个提供商时,应填写它所需的全部字段,并保持 config/cookie-consent.ts 中对应项目的键名一致。
第三方脚本的加载方式取决于 Cookie 同意模式:
silent:直接加载已经配置好的第三方脚本。当前项目使用此模式。prompt:只有用户同意对应统计项目后才加载。disabled:不显示同意界面,也不注入第三方统计脚本。
第一方 /js/analytics.js 不受第三方统计分类控制。即使用户拒绝第三方统计脚本,只要第一方统计仍然开启且请求符合站点规则,浏览器仍会请求 /api/analytics/collect。上线前应根据目标市场和实际采集字段评估隐私告知与同意要求,详见 Cookie 同意。
关闭第一方统计
不需要第一方统计时,将总开关设为 false:
analytics: {
enabled: false,
}重新构建并部署后:
- 页面不再加载
/js/analytics.js。 POST /api/analytics/collect不再注册。- Dashboard 中的统计菜单和页面不再显示,对应后台接口也不再有效。
- 已在
analytics-events中等待处理的消息会被确认并丢弃。 - 数据保留任务停止,不会继续删除历史统计数据。
关闭总开关不会自动删除 ANALYTICS_DB 中已有的数据。如需物理清除,必须先确认备份、保留要求和恢复方案,再对数据库执行单独的数据清理操作。
使用独立的采集域名
默认脚本向同源的 /api/analytics/collect 上报。需要使用独立采集域名时,在构建统计脚本前设置:
VITE_SAAVO_COLLECT_API_HOST=https://analytics.example.com
VITE_SAAVO_COLLECT_API_ENDPOINT=/api/analytics/collect这两个值在构建 /js/analytics.js 时写入,不是运行时 Secret。独立采集域还需要正确处理来源校验、域名配置和浏览器跨域策略。同源部署不要设置它们。
上线检查
-
site.id已换成新的 UUID。 -
VITE_SITE_URL指向当前站点,allowedDomains只包含实际需要的额外域名。 -
ANALYTICS_DB、ANALYTICS_QUEUE和analytics-events已在自己的 Cloudflare 账户中创建并正确绑定。 - 新数据库已初始化,且没有对现有数据库重复执行会删表的
schema/analytics.sql。 - 前台页面能加载
/js/analytics.js,采集请求返回202。 -
/dashboard和其他排除路径不会产生采集请求。 - 四个示例漏斗已经删除或改成产品真实路径。
- Cron 配置与
ANALYTICS_RETENTION_CRON一致,清理日志中没有持续出现积压告警。 - 未使用的第三方配置保持为空,使用的提供商已与 Cookie 同意策略对应。
- 隐私政策已经说明实际采集的数据、用途和保留时间。
建议使用未设置 saavo.disabled 的浏览器访问前台,再以管理员身份打开统计后台核对数据。不要用 Dashboard 自身的访问量验证采集,因为 /dashboard 默认被排除。