流量統計

設定第一方流量統計、事件收集、轉換漏斗、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 路由變更
第一方資料收集 APIPOST /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、趨勢與分組統計。

資料寫入流程

一次正常的瀏覽器資料收集會經過下列流程:

  1. 呈現頁面時,先檢查統計總開關、網站狀態、造訪網域與忽略路徑,符合條件才載入 /js/analytics.js。
  2. 瀏覽器指令碼收集頁面瀏覽、自訂事件或效能指標,並傳送至 POST /api/analytics/collect。
  3. 收集 API 檢查網站 ID、造訪次數的劃分方式、來源網域、忽略規則、機器人流量與請求頻率。
  4. 通過檢查後,事件先寫入 analytics-events 佇列,再由取用者(Consumer)寫入 ANALYTICS_DB。
  5. 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 資源都屬於目前專案。

  1. 為 site.id 產生新的 UUID。
  2. 確認 VITE_SITE_URL 是目前環境的網站位址。統計服務會自動允許該位址的主機名稱,本機網站也會自動允許 localhost 與 127.0.0.1。只有需要接受其他網域時,才在 allowedDomains 補上,預設保持空陣列即可。
  3. 本機環境由 saavo:init 初始化 ANALYTICS_DB。首次遠端部署時,則由 deploy:init 建立並繫結資料庫與 analytics-events Queue。
  4. 後續資料庫變更寫入 schema/migrations,再透過 db:migrate:local 或部署指令碼套用。不要將初始化檔案當成增量遷移重複執行,詳細流程請參考在本機執行。
  5. 將 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_completed
  • login_completed
  • checkout_session_created
  • affiliate_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 預設已被排除。

常見問題

接下來