流量統計
設定第一方流量統計、事件收集、轉換漏斗、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 路由變更 |
| 第一方資料收集 API | 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。 - 收集 API 檢查網站 ID、造訪次數的劃分方式、來源網域、忽略規則、機器人流量與請求頻率。
- 通過檢查後,事件先寫入
analytics-events佇列,再由取用者(Consumer)寫入ANALYTICS_DB。 - Dashboard 直接查詢保留期間內的統計資料,因此佇列訊息尚未處理時,後台不會立即顯示剛送出的事件。
收集 API 成功接收訊息後,會回傳 202。佇列寫入資料庫失敗時,最多嘗試 3 次。最後一次仍失敗,就會記錄錯誤並放棄該訊息,目前專案沒有為統計佇列設定死信佇列(Dead-letter Queue)。
預設設定
第一方統計設定位於 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及其子路徑不會載入第一方統計指令碼,收集 API 也不會接受這些路徑的資料。- 保留 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 不再顯示統計選單與頁面,對應的後台 API 也不再有效。
- 已在
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只包含實際需要的額外網域。 - 已在自己的 Cloudflare 帳戶建立
ANALYTICS_DB、ANALYTICS_QUEUE與analytics-events,並正確繫結。 - 新資料庫已初始化,且未對既有資料庫重複執行會刪除資料表的
schema/analytics.sql。 - 前台頁面可以載入
/js/analytics.js,收集請求會回傳202。 -
/dashboard與其他排除路徑不會產生收集請求。 - 四個範例漏斗已刪除,或改成產品實際的路徑。
- Cron 設定與
ANALYTICS_RETENTION_CRON一致,清理記錄中沒有持續出現資料堆積警示。 - 未使用的第三方設定維持空值,使用中的供應商已對應 Cookie 同意政策。
- 隱私權政策已說明實際收集的資料、用途與保留時間。
建議使用未設定 saavo.disabled 的瀏覽器造訪前台,再以管理員身分開啟統計後台核對資料。不要以 Dashboard 本身的造訪量驗證收集功能,因為 /dashboard 預設已被排除。
常見問題
接下來
- 調整第三方指令碼的同意方式 → Cookie 同意管理
- 查看統計訊息如何寫入資料庫 → 背景工作
- 查看保留期間清理工作與 Cron 設定 → 排程工作
- 初始化與遷移本機統計資料庫 → 在本機執行
- 查看管理員入口與權限 → 管理後台