[CODE]

點擊從來就不是訂房

把 GA4 聯絡按鈕點擊換成 Supabase 真實訂房資料——解開 gclid 限制、讓兩個 Google Ads 帳號透過同一個 GA4 property 分流不污染,再把輸出拆成「收到訂單」和「實際入住」兩個視角。

1 min read AI 生成
analytics ga4 automation typescript google-ads

自建廣告系統服務恆春民宿業者,整個前提只有一句話:廣告優化的目標函數必須比 OTA 的量測更準。OTA 的優勢是三件事同時疊加:龐大的廣告預算、長年累積的品牌信任,加上專職的行銷團隊全時間運作。佣金遠比大多數小業者意識到的高——通常要跑完整個旺季把帳算清楚,才會真正感受到那個數字的重量。自建的理由不是要在預算上打贏 OTA,而是可以擁有更精準的量測訊號、對齊每間房子真實的定價結構,並且持續複利累積。但這套廣告引擎跑了好幾個月,優化依據的那個數字一直指向錯誤的東西。

不是追蹤技術壞了。GA4 把每一個站內互動都收得乾乾淨淨——電話號碼點擊、LINE 連結、WhatsApp、Facebook、訂房按鈕——並且透過 stay_type 自訂參數即時分辨兩間物件的流量。問題是結構性的:真正成交的環節在電話或通訊軟體裡發生,GA4 完全看不到。唯一能自動流進系統的,是訪客在頁面上按了聯絡 CTA 的次數。這個湊合方案一路撐到 Booking Tracker 上線。

替代指標壞在哪裡

站內聯絡點擊率不是一個穩定的比例。它跟你試圖優化的那些決策變數高度相關——這才是真正的問題所在。

淡季帶進的是瀏覽意圖較低的流量,完成訂房的比例本就偏低。旺季高價日期讓更多人在比較、猶豫、中途離開。一間快滿的房子,不管廣告再好,能帶進的新訂單本來就有限。這三個因素都會在廣告實際表現沒有任何變化的情況下,改變點擊轉換率。

後果是:旺季帶進大量低意圖點擊的廣告,可以跟淡季真正帶進客源的廣告,在報表上產生完全相同的點擊數。數字一樣,業務意義完全相反。用這個當優化依據,產生的不是隨機雜訊——而是會系統性強化你原本就相信的偏見的雜訊。

Booking Tracker

資料登入端的設計細節另有一篇文章。簡短版:主要使用者是業者的媽媽,在 iPhone 上操作,所以 UX 的限制不是偏好、是硬約束。需要超過幾個 tap 才能完成的流程,在真實日常使用下活不過第一週。技術棧是 Next.js 16 PWA,Supabase 處理認證和資料庫,Google OAuth 讓多個帳號共享同一份業者資料。選 PWA 而非原生 App,是因為 Apple Developer Program 每年有費用、加上審核排程讓每次更新都變成多天的等待。Supabase 和 Vercel 在這個量級都跑在免費方案。

每筆訂房登錄後,依序發生三件事:寫入 Supabase bookings 資料表、在業者的共用「訂房日誌」Google 日曆建立事件、發出一個 GA4 Measurement Protocol 事件。第三步需要一些架構上的決策。

gclid 的牆

訂房記錄是事後登記的。客人早已掛掉電話、訂好日期。沒有 gclid——Google Ads 用來歸因廣告點擊的那個識別碼。Google Ads Conversion API 的 uploadClickConversions 端點強制要求 gclid / wbraid / gbraid 三者之一,沒有就直接回 400 Invalid。這條路堵死了。

GA4 Measurement Protocol 不需要 gclid。傳一個匿名的 client_id 就能把事件送進 GA4 property,Google Ads 再從 GA4 自動同步轉換資料。歸因走的是 modeled attribution 而非 click-path 精確對應——精度差一些,但對 Smart Bidding 的訊號品質夠用。

接著是另一個約束:兩間物件共用同一個 GA4 property。如果兩個都送一樣的 booking_confirmed,各自的 Google Ads 帳號就會互相汙染對方的轉換資料。解法是物件專屬的事件名稱——每間民宿各有獨立的事件名稱對應自己的帳號——各自只匯入自己的事件。兩個 Ads 帳號、一個共用 GA4 property,完全不污染。GA4 因為事件名稱不重疊,可以精準分流。

轉換價值在 src/lib/ga4.ts 的提交端動態計算:晚數乘以各訂房類型的均價。包棟每晚定價是散客的數倍,兩晚包棟送給 Smart Bidding 的轉換價值遠高於一晚散客。替代指標完全無法表達這個差異——兩種訂房在 GA4 裡都長得像同一個聯絡按鈕事件,看不出任何房價差距。

資料管道現在看到的

週報分析引擎(分析 pipeline 中的 fetch.ts)現在在所有 GA4 Data API 呼叫的同時,也直接查詢 Supabase,把訂房記錄組成兩個獨立視角。

收到訂單(received)created_at——媽媽登記的時間——為鍵。無論入住日在什麼時候,本週記錄到的所有訂房都算在這裡。這是漏斗健康指標,也是廣告優化的主要訊號。收到訂單下滑而站內流量和聯絡事件沒有異動,問題在轉換,不在頁面。

實際入住(staying)checkin_date 為鍵,記錄本週真正辦理入住的組數。這是房務管理的操作快照——現在的住房率、近期現金流。兩個視角的回應時間完全不同:收到訂單的數字可以在本週觸發廣告調整;實際入住更多用來幫業者判斷定價和人力安排。

把兩者壓成一個數字會把兩個業務問題混在一起,哪個都回答不清楚。收到訂單旺、實際入住少的週,不是漏斗壞掉的訊號——是訂單落在更遠的日期。沒有拆開,這個型態讀起來就是雜訊。它不是。

第三個視角把住房率拉開成三個曆月——上個月、這個月、下個月——每個物件各自算入住晚數,再拆成平日和假日。上月提供對比基準,本月顯示當前狀況,下月顯示未來已確認的訂房。業者看到下個月的空白平日,可以在廣告需要填補之前就調整定價。

餵給 LLM 產週報用的 business-context 文件也同步更新了。舊版把聯絡按鈕點擊設為主要 KPI。新版以「收到訂單」為主要轉換指標,聯絡點擊重新定位為站內意圖訊號——對漏斗分析有用,對廣告 ROI 的計算沒有意義。同一份底層資料,context 描述的目標函數不同,LLM 生成的報告性質就完全不同。只更新資料管道、不更新 context 文件,產出的週報會用真實訂房數字說話,但 LLM 的推論框架還停留在點擊替代思維。

準確訊號在兩個方向都有效

替代指標遮蔽了兩種型態:點擊多但實際訂房少的廣告,在舊報告裡看起來表現正常;流量不大但訂房轉換率高的廣告,在點擊基準下看起來績效平庸。兩種型態都是廣告決策上可行動的訊號,但替代指標的平均值把底下的差異磨平了。

自建廣告系統對抗 OTA 這條路,訊號的品質是前提,不是加分項。OTA 有完整的行銷基礎設施、多年的訂房行為資料、和一套已經懂得如何為每種物件類型優化排名的系統。佣金是使用那套機器的費用。在這個客戶規模下,以前根本沒有足夠便宜的方式自建這套量測——是 AI 進了 stack 才讓這件事的成本變得可行。一旦訊號存在、並且校準到真實訂房和真實定價,Smart Bidding 就能學到 OTA 看不到的東西:哪個廣告素材和關鍵字組合帶來真正入住的客人,以及這些住宿每晚的實際價值。

報表現在算的是一個結果,不是一個起點。