GA4 自訂事件實作:7 個事件把監視器升級成「看誰是真讀者」的版本
GA4 預設只追 page_view。意思是你只會知道「有人來」,但不知道他來了之後幹嘛——滑了多少、停了多久、點了什麼、有沒有點到關鍵 CTA。
這篇紀錄我在 site.js 加 7 個自訂事件的完整實作:選哪些事件、怎麼寫 JS、為什麼要用 typeof gtag 守衛、怎麼在 GA4 後台「標示為主要事件」、怎麼用 Realtime 驗證有沒有真的送出。
這是 #4 預告過的續集。終於把監視器升級成能告訴你「誰是真讀者、誰只是路過」的版本。
1. 為什麼這篇值得寫
GA4 自訂事件教學一抓一大把,但 90% 的文章只給你三件事:
- 「在 head 貼這段 gtag」
- 「呼叫 gtag('event', '...', {{...}})」
- 「在 GA4 後台標示為主要事件」
問題是:該追哪 7-10 個事件?該怎麼選?scroll 要不要 throttle?outbound 怎麼判斷?萬一 GA 沒載入會不會炸?這些「實際下手的決策」幾乎沒人寫。
這篇給你的是「對個人站/小型內容站」這個 scope 的具體選擇,不是給 e-commerce 或 SaaS 的完整 taxonomy。
2. 第一刀:GA4 預設只追 page_view 的盲點
你只裝 gtag 沒加自訂事件時,GA4 預設會自動追幾個基本事件:page_view、session_start、first_visit、user_engagement(互動超過 10 秒)。
看起來夠用,其實只能回答「有沒有人來、來幾次、是不是新訪客」——這幾乎都是流量側的數字。答不出來的問題:
- 他是看完才走,還是開頭就關
- 他停了 5 秒還是 5 分鐘
- 他點到我的服務 CTA 沒
- 他跳到哪個外部連結了
這些「行為訊號」才是判斷「誰是真讀者」的關鍵。要回答上面這 4 個問題,至少要自訂 7 個事件。
3. 第二刀:7 個事件的選擇邏輯
GA4 自訂事件上限 50 個,看起來很多,實際上每加一個就多一份維護成本。對個人內容站,這 7 個夠用:
| 事件名 | 觸發 | 為什麼追 |
|---|---|---|
scroll_50 | 滑到 50% | 區分「點開就關」vs「至少看一半」 |
scroll_90 | 滑到 90% | 真讀者(讀到底) |
dwell_60s | 停 60 秒 | 真讀者 vs 路過(10 秒 user_engagement 太寬) |
service_cta_click | 點諮詢 CTA | 1on1 漏斗最頂端的意圖訊號 |
outbound_click | 點站外連結 | 看讀者跳到哪、引用哪些工具 |
subscribe_submit | 訂閱 form 成功送出 | Email 收集漏斗末端 |
theme_toggle | 切淺/深色 | 視覺設計決策依據(多少人真的會切) |
這 7 個分成三類:閱讀深度(scroll/dwell)、意圖訊號(cta/subscribe)、行為觀察(outbound/theme)。每類至少 2 個才能交叉看。
4. 第三刀:site.js 實作(最小可用 pattern)
我整站只有一個 assets/site.js,已經被 page_shell 引入所有頁面。所以加事件邏輯改一個檔,全站生效。
(1)為什麼一律先 typeof gtag === 'function'
GA 載入過程可能被擋(廣告封鎖外掛、CSP 漏配、網路問題)。如果 gtag 沒定義,呼叫它會 throw ReferenceError,整個 script 就掛了——後面的功能也跟著死。
// ❌ 直接呼叫
gtag('event', 'theme_toggle', { mode: cur });
// ✅ 先檢查
if (typeof gtag === 'function') {
gtag('event', 'theme_toggle', { mode: cur });
}
前者在 GA 沒載的情境會炸。後者 GA 沒載就靜默跳過,網站功能不受影響。這是「event tracking 不該是 critical path」的基本原則。
(2)scroll 要 throttle(不然會卡)
scroll event 一秒可以觸發幾百次。如果每次 scroll 都跑「計算 % + 比對 milestone + 呼叫 gtag」,捲動會明顯卡。要用 requestAnimationFrame throttle:
const fired = new Set();
let ticking = false;
function checkScroll() {
ticking = false;
const pct = (window.scrollY + window.innerHeight)
/ document.documentElement.scrollHeight * 100;
for (const m of [50, 90]) {
if (pct >= m && !fired.has(m)) {
fired.add(m);
gtag('event', 'scroll_' + m, { page_path: location.pathname });
}
}
}
window.addEventListener('scroll', () => {
if (!ticking) { requestAnimationFrame(checkScroll); ticking = true; }
}, { passive: true });
重點:fired Set 確保每頁只送一次(同個 milestone 不會重送);passive: true 讓瀏覽器不等 listener 就先捲動,畫面更順。
邊界處理:頁面比視窗短時(短文章),scroll% 永遠到不了 50/90,要在 init 時補一次「短頁直接記 90」,否則短文章的閱讀深度資料會全空。
(3)click delegation:一個 listener 處理所有連結
要追 service CTA + outbound 兩種 click,不要對每個 a 標籤掛 listener(頁面有幾十個連結時記憶體會吃緊)。用事件代理:
document.addEventListener('click', (e) => {
const a = e.target.closest('a');
if (!a) return;
const href = a.getAttribute('href') || '';
// Service CTA
if (href.includes('contact.html')
&& href.includes('topic=consultation')) {
gtag('event', 'service_cta_click', {
page_path: location.pathname,
link_text: (a.textContent || '').trim().slice(0, 60),
});
}
// Outbound
try {
const url = new URL(a.href, location.href);
if (url.host && url.host !== location.host) {
gtag('event', 'outbound_click', {
link_domain: url.host,
link_url: url.href.slice(0, 200),
});
}
} catch (_) {}
});
重點:
- service CTA 用 href 偵測(href 包含 contact.html?topic=consultation)。比 class selector 穩——之後改 button 樣式不會壞
- outbound 用 URL 解析(new URL(...))比 startsWith('http') 安全——能處理相對路徑、anchor 等邊界
- url.href.slice(0, 200) 避免送超長 URL 進 GA(GA event param 有長度上限)
(4)dwell 一行解決
setTimeout(() => gtag('event', 'dwell_60s', {
page_path: location.pathname
}), 60_000);
60 秒後送一次。讀者中途離開頁面也沒關係——GA4 預設會把 queued event 在 unload 時送出,不會掉。
5. 第四刀:GA4 後台「標示為主要事件」
code 上線後 GA4 會自動收事件,但只是「事件」,要轉成「主要事件(=轉換)」才會進報表的轉換欄。
路徑:GA4 管理 → 資料顯示欄 → 事件 → 找到事件名 → 切換右側「標示為主要事件」
我這 7 個事件的標示策略:
| 事件 | 標示為主要事件? |
|---|---|
service_cta_click | ✅ 主要事件(=潛在客戶) |
subscribe_submit | ✅ 主要事件(=Email 取得) |
scroll_90 | ✅ 主要事件(=完讀,作為內容品質訊號) |
scroll_50 / dwell_60s | ❌ 當「參與訊號」,不算轉換 |
outbound_click / theme_toggle | ❌ 當「行為觀察」,純資料 |
重點:不要把所有事件都標主要事件。標太多會讓 GA4 的「轉換」報表失焦——什麼都重要就等於什麼都不重要。3 個夠了。
6. 第五刀:驗證(GA4 Realtime)
GA4 自訂事件最大的痛點:你看不到「事件有沒有真的送出」直到 24 小時後標準報表開始有資料。但 Realtime 面板會即時顯示。
- 推 site.js 上線(Vercel 部署完約 1-2 分鐘)
- 新分頁打開 coolkidlab.com
- GA4 後台 → 報表 → 即時 → 「過去 30 分鐘事件」widget
- 回到網站做事件:點 theme toggle → 滑到底 → 等 60 秒 → 點服務 CTA → 切換 GA4 即時面板看
- 事件應該在 5-15 秒內陸續出現:theme_toggle、scroll_50、scroll_90、dwell_60s、service_cta_click
如果某個事件沒出現:開 F12 console,輸入 typeof gtag——如果回 function 但事件沒送,那是 listener 沒掛上;如果回 undefined,那是 GA 沒載入(廣告封鎖 / CSP 擋 / network 失敗,三選一)。
7. 戰績 + 我學到什麼
| 指標 | 做之前 | 做之後 |
|---|---|---|
| 自訂事件數 | 0 | 7 ✅ |
| 主要事件(轉換) | 0 | 3 ✅ |
| 能回答「誰是真讀者」 | ❌ | ✅ |
| site.js 增加行數 | — | 約 +60 行 |
| cache buster bump | 舊 hash | 自動(asset_hash 偵測內容變化) |
我從這個過程學到三件事:
- 「該追哪些事件」是內容策略題,不是技術題。技術只是把策略翻譯成 code。先想清楚「我要回答什麼問題」再寫 listener。
- Event tracking 不該影響網站可用性。每個 gtag 呼叫前都要 typeof 檢查——GA 沒載入時網站要還能正常運作。
- 事件少而精勝過事件多而亂。7 個事件 + 3 個主要事件,比 30 個事件全部標主要事件好用 10 倍。
8. 給跟我一樣從 0 開始的人:5 步驟 checklist
- 列出 5-10 個「想回答的問題」(誰是真讀者?哪些頁有人點 CTA?訂閱漏斗哪一段掉?)
- 每個問題對應 1-2 個事件(scroll / dwell / click / submit),不要追動詞太重複的
- 在 site.js(或你的全站 JS)加 listener,每個 gtag 呼叫一律 typeof 檢查
- Push 上線後,回 GA4 Realtime 面板手動驗證每個事件都送出
- 在 GA4 後台只把「跟轉換有關」的 2-3 個事件標主要事件,其他純資料留著
9. 下一步
GA4 自訂事件裝完,監視器升級成「能告訴你誰是真讀者」的版本。但事件收進來只是「能看」,不是「能用」。真正會用要兩步:
- 等 7-14 天累積資料量(個人站每天才 10-50 個訪客,太短沒統計意義)
- 在 GA4「探索」報表設兩個自訂分析:(a) 每篇文章的 scroll_90 完讀率排名 (b) service_cta_click 的來源頁面分布
白老鼠實驗下一篇 #6 會回到 GSC 看一週後的索引曲線——強送配額分散送之後,曲線是否變平緩、未索引頁有沒有自然進索引、第一個搜尋查詢出現了沒。
看完這篇之前先確認:
- GA4 已部署但只看到 page_view
- 想追 CTA click / scroll 深度
- 想用 GA4 取代付費分析工具
- 連 GA4 都還沒裝的人
- 想用 GTM UI 點點點不寫 code 的人
- 結構複雜的電商(建議直接上 GTM)
- 事件名稱用中文導致報表壞掉
- 重複觸發不去重(一頁打 10 次同事件)
- dev 測試但沒開 GA4 debug mode
相關閱讀
這篇背後的真實開發過程記錄在 Build Log。
搜尋標籤:ga4、events、conversion、site-js。
本篇為個人學習與實驗紀錄。GA4 介面與事件 API 持續變動,本文 code 範例不保證在你的網站環境完全相同,請依自身狀況實驗驗證。本站不接 YMYL 高風險站、不做 PBN、不做品牌矩陣 SEO。