[CODE]

GA4 是基礎設施,不是儀表板

五間恆春民宿共用一套廣告優化管線。其中一間的 GA4 property 從網站上線就是空的——廣告帳戶一直在花錢,優化引擎在讀零。要讓埋點撐住持續迭代的事件邏輯、隔離 staging 流量、讓下一間能直接套用,架構比貼一段 script 複雜得多。

1 min read AI 生成
astro analytics ga4 client-work

五間恆春民宿共用一套廣告優化管線。這套管線的優化循環需要 GA4 事件作為信號。在這個市場能用的只有 click proxy:電話點擊、LINE 聯繫、email 點擊、訂房頁造訪。這些不是真實訂房,是購買意圖的代理信號。代理信號有雜訊——點了電話不代表要訂房,可能只是確認有沒有人接。但每月幾百次的造訪量,幾週就能累積出有統計意義的樣本。等真實訂房紀錄達到同樣的量,要花幾年。廣告引擎需要有東西可以優化,click proxy 滿足這個條件。

五間之中,四間的事件一直在跑。第五間的 GA4 property 從網站上線以來就是空的:網站在線、廣告帳戶在花錢、優化引擎在讀零。不是設定錯誤,是從來沒有埋過。補上這間的埋點,要同時滿足三個條件:撐住事件邏輯的持續迭代、隔離 staging 的測試流量、讓排隊等待 GA4 的另外兩間可以直接套用。三個條件加在一起,比貼一段 script 複雜得多。

拆成兩個元件,因為兩半的壞法不一樣

Google 的官方做法是一段 <script> 塞進 <head>。我拆成兩個 Astro 元件,理由是操作層面的,不是風格上的。

GtagHead.astro 是 bootstrap,放在 <head> 裡。工作內容:用 Google Ads 的 tag ID 載入 gtag.js(Google 建議 Ads 啟用時用 AW- ID 載入,讓 Ads destination 在第一次 pageview 之前就初始化),再用兩個獨立的 gtag('config') 呼叫分別註冊 GA4 和 Google Ads 兩個 destination。這個元件只有一個要求:在每一頁、每一次請求都正確執行。部署好之後幾個月不用動。

Ga4Tracking.astro 放事件邏輯,掛在 <body> 末尾。放在 body 末尾不是隨意的選擇——這個 script 要對 DOM 下 query,body 末尾能確保元素存在。追蹤五個信號:phone_clickline_clickemail_clickbooking_clickscroll_depth。這一半不停在變:發現一個值得追蹤的行為模式就加一個事件,初始閾值設錯了就調,帶雜訊的參數就移掉。

兩半放同一個檔案,每次改事件邏輯都在動那個不能出問題的 bootstrap。拆開之後,bootstrap 有穩定的風險邊界,事件層有自由迭代的空間。事件追蹤的 regression 爆炸半徑,就限制在事件追蹤本身。

捲動深度的實作有一個不明顯的細節。網站的 global CSS 設了 body { overflow-x: hidden },這讓 body element 成為 scroll container,window.scrollY 無論捲多深都是 0。深度計算要同時讀 document.documentElement.scrollTopdocument.body.scrollTop,事件監聽也要同時掛在 windowdocument 兩個 target 上,才能確保接得到。最初沒有這個 workaround,折疊線以上的捲動全部偵測不到。

booking_click 送到 GA4,但刻意排除在 Google Ads 轉換之外。造訪訂房頁是漏斗步驟,不是聯繫行動本身。把這個信號送進 Ads 當轉換,優化的目標會變成頁面導航,不是真正拿起電話的訪客。「更多轉換信號」不一定更好——信號必須對應到真正想優化的結果。

這裡有一條被寫進 codebase 的規則:每一個可點擊的聯繫入口——電話、LINE、email、sticky bar 的按鈕、訂房流程的 CTA——都必須在加入該元素的同一個 commit 裡加上對應的 handler。沒有追蹤的 CTA 當成 bug 對待。原因是診斷問題:靜默的漏斗缺口和真正轉換率低,在數據上產生一模一樣的症狀。「LINE 詢問這個月很少」,看不出來是沒有人點、還是根本沒有在追蹤。追蹤完整性要靠結構強制執行,不能靠記憶。

Measurement ID 是部署的屬性,不是元件的屬性

把 measurement ID 寫死在 head snippet 對單一網站、單一環境有效。這個假設在兩個地方會斷。

第一是 staging。開 staging 環境預覽變更時,測試 session 會進正式 GA4 property,污染 session 計數、流量來源歸因、行為數據。沒有 flag 可以切換隔離。只能忍受污染的數據,或乾脆不開 staging——對面向客戶的系統來說,兩個選項都不行。

第二是複製。另外兩間沒有 GA4 的民宿在等著上。複製再客製的結果是兩份 code 從同樣的起點開始分歧,更新一邊忘記另一邊是遲早的事。三個業主同時在線,這個代價乘以三。

src/config/analytics.ts 是單一真相來源:GA4 measurement ID、Google Ads conversion ID、每個聯繫管道的 conversion label 全部集中在這裡。兩個元件都從這裡讀。三個業主的元件檔案完全一樣,不同的只有 config 裡的值。客製化有明確邊界,複製只是填一份新的 config。

GA4 是管線,週報才是產品

業主不會去開 GA4。這不是設計的缺口,而是設計要繞開的約束。

數據的出口是每週一份自動化週報:哪個流量來源帶來最多訂房意圖的點擊、哪個房型頁讓訪客在看到定價前就離開、流量和上週同期比是漲是跌。三個數字,白話文,各自對應一個業主真的能做的決定。週報是另一個 commit 的事,這個 commit 是週報能跑起來的地基。

過去這個規模的民宿,根本沒有辦法同時負擔三條成本:維護分析的人、操作廣告帳戶的人、每週把數據翻譯成業主讀得懂語言的人。AI 工具把三條同時壓到對兩三間客房的業主也合理的水準。但「合理」的前提是上游數據在跑。下游管線早就建好了,上游今天才補上。

數據從現在開始進來了。