Coolkid mascot CoolkidLab

SEO 菜鳥成長史 · #5 · 最後更新:2026-05-11

GA4 自訂事件實作:7 個事件把監視器升級成「看誰是真讀者」的版本

GA4 預設只追 page_view。意思是你只會知道「有人來」,但不知道他來了之後幹嘛——滑了多少、停了多久、點了什麼、有沒有點到關鍵 CTA。

這篇紀錄我在 site.js 加 7 個自訂事件的完整實作:選哪些事件、怎麼寫 JS、為什麼要用 typeof gtag 守衛、怎麼在 GA4 後台「標示為主要事件」、怎麼用 Realtime 驗證有沒有真的送出。

這是 #4 預告過的續集。終於把監視器升級成能告訴你「誰是真讀者、誰只是路過」的版本。

1. 為什麼這篇值得寫

GA4 自訂事件教學一抓一大把,但 90% 的文章只給你三件事:

問題是:該追哪 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 秒)。

看起來夠用,其實只能回答「有沒有人來、來幾次、是不是新訪客」——這幾乎都是流量側的數字。答不出來的問題:

這些「行為訊號」才是判斷「誰是真讀者」的關鍵。要回答上面這 4 個問題,至少要自訂 7 個事件。

3. 第二刀:7 個事件的選擇邏輯

GA4 自訂事件上限 50 個,看起來很多,實際上每加一個就多一份維護成本。對個人內容站,這 7 個夠用:

事件名觸發為什麼追
scroll_50滑到 50%區分「點開就關」vs「至少看一半」
scroll_90滑到 90%真讀者(讀到底)
dwell_60s停 60 秒真讀者 vs 路過(10 秒 user_engagement 太寬)
service_cta_click點諮詢 CTA1on1 漏斗最頂端的意圖訊號
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 (_) {}
});

重點:

(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 面板會即時顯示。

  1. 推 site.js 上線(Vercel 部署完約 1-2 分鐘)
  2. 新分頁打開 coolkidlab.com
  3. GA4 後台 → 報表 → 即時 → 「過去 30 分鐘事件」widget
  4. 回到網站做事件:點 theme toggle → 滑到底 → 等 60 秒 → 點服務 CTA → 切換 GA4 即時面板看
  5. 事件應該在 5-15 秒內陸續出現:theme_toggle、scroll_50、scroll_90、dwell_60s、service_cta_click

如果某個事件沒出現:開 F12 console,輸入 typeof gtag——如果回 function 但事件沒送,那是 listener 沒掛上;如果回 undefined,那是 GA 沒載入(廣告封鎖 / CSP 擋 / network 失敗,三選一)。

7. 戰績 + 我學到什麼

指標做之前做之後
自訂事件數07 ✅
主要事件(轉換)03 ✅
能回答「誰是真讀者」
site.js 增加行數約 +60 行
cache buster bump舊 hash自動(asset_hash 偵測內容變化)

我從這個過程學到三件事:

  1. 「該追哪些事件」是內容策略題,不是技術題。技術只是把策略翻譯成 code。先想清楚「我要回答什麼問題」再寫 listener。
  2. Event tracking 不該影響網站可用性。每個 gtag 呼叫前都要 typeof 檢查——GA 沒載入時網站要還能正常運作。
  3. 事件少而精勝過事件多而亂。7 個事件 + 3 個主要事件,比 30 個事件全部標主要事件好用 10 倍。

8. 給跟我一樣從 0 開始的人:5 步驟 checklist

  1. 列出 5-10 個「想回答的問題」(誰是真讀者?哪些頁有人點 CTA?訂閱漏斗哪一段掉?)
  2. 每個問題對應 1-2 個事件(scroll / dwell / click / submit),不要追動詞太重複的
  3. 在 site.js(或你的全站 JS)加 listener,每個 gtag 呼叫一律 typeof 檢查
  4. Push 上線後,回 GA4 Realtime 面板手動驗證每個事件都送出
  5. 在 GA4 後台只把「跟轉換有關」的 2-3 個事件標主要事件,其他純資料留著
Coolkid AI Lab GA4 過去 7 天統計:31 位活躍使用者、363 次事件、台灣 17 / 美國 7 / 香港 2
事件裝完 7 天的實際數據:31 個活躍使用者 / 363 次事件 / 31 個新使用者。沒這 7 個自訂事件,363 這個數字就只會是 31 個 page_view,全部白看。事件少而精,數據才有訊號。

9. 下一步

GA4 自訂事件裝完,監視器升級成「能告訴你誰是真讀者」的版本。但事件收進來只是「能看」,不是「能用」。真正會用要兩步:

  1. 等 7-14 天累積資料量(個人站每天才 10-50 個訪客,太短沒統計意義)
  2. 在 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搜尋標籤:ga4eventsconversionsite-js

本篇為個人學習與實驗紀錄。GA4 介面與事件 API 持續變動,本文 code 範例不保證在你的網站環境完全相同,請依自身狀況實驗驗證。本站不接 YMYL 高風險站、不做 PBN、不做品牌矩陣 SEO。

← 回 SEO 菜鳥成長史

⚠ 本站所有內容僅供教育與研究用途,不構成投資建議,不保證任何獲利。投資有風險,使用者須自行判斷並承擔結果。