靜態站的能力邊界與膠水層
靜態站的能力邊界是一條很硬的線:使用者的瀏覽器把 HTML/CSS/JS 抓下來之後,所有程式碼都在使用者那一端執行,沒有任何一行程式跑在你控制的機器上。GitHub Pages、Netlify、Cloudflare Pages 這類託管都是把檔案原封不動送給瀏覽器,中間沒有一個「屬於你、會執行邏輯、看得到每個請求」的伺服器。這條線決定了靜態站能做什麼、不能做什麼。
為什麼靜態站量不到自己的流量
流量統計的本質是「把每一次瀏覽記在某個你能事後查詢的地方」。傳統有伺服器的網站天生做得到,因為每個請求都會經過你的伺服器,伺服器在回應頁面的同時,順手在 access log 寫下一行「誰、什麼時候、看了哪一頁」。這行 log 是伺服器端的副產品,不需要前端配合。
靜態站沒有這個副產品。GitHub Pages 確實有伺服器在送檔案,但那是 GitHub 的伺服器、不是你的——你拿不到它的 access log,也不能在它上面跑任何統計邏輯。所以對靜態站來說,「有人來看過」這個事實,在預設情況下沒有留在任何你查得到的地方。頁面被看完、瀏覽器關掉,紀錄就消失了。
要把這個事實留下來,唯一的切入點是頁面裡的 JavaScript——那是靜態站上唯一會執行的程式碼。做法是讓這段 JS 在頁面載入時,主動送一個請求到一個由你掌握的接收端,接收端把這次瀏覽記下來。這種「瀏覽器主動回報一則事件」的請求,慣例上叫做 beacon。
膠水層補的是哪一塊
膠水層(glue layer)是一段掛在別人免費平台上、只在被呼叫時才執行的伺服器端邏輯,用來補上靜態站缺的那一小塊伺服器能力。它的形態是一個函式,而非一台你要維護的主機:平時不佔資源、不計費,有請求打進來時才醒過來跑幾百毫秒,跑完就休眠。
用流量統計來看這個分工會很清楚:
- 靜態站(瀏覽器端):負責偵測「這一頁被看了」,並發出 beacon。這段程式碼在使用者瀏覽器跑。
- 膠水層(伺服器端函式):負責接住 beacon,把這次瀏覽寫進某個儲存體。這段程式碼在免費平台跑,是你控制的接收端。
- 儲存體:真正記住資料的地方。可以是一張 Google Sheet、一個免費資料庫、或平台自帶的 key-value 儲存。
膠水層補的就是中間那一塊——一個「屬於你、會執行、看得到每個 beacon」的接收端。它不必是完整後端,只要能接住請求、寫一筆資料就夠了。本指南模組二用 Google Apps Script 當這個膠水層,模組三用 Google Sheet 當儲存體。
client beacon 架構怎麼運作
一次完整的流量紀錄走三步,全部由瀏覽器發起:
- 使用者打開你的一篇文章,瀏覽器載入頁面,頁面裡的 JS 開始執行。
- JS 組出一則事件(例如「路徑
/posts/foo/、時間、referrer」),送一個 HTTP 請求到膠水層的網址。 - 膠水層收到請求,把這則事件 append 進儲存體,回一個簡短回應。瀏覽器不需要等這個回應、也不需要用它做任何事。
第三步的「不需要等回應」是 beacon 跟一般 API 呼叫最重要的差別。使用者是來讀文章的,不是來等統計寫完的——beacon 應該是 fire-and-forget(送出就忘):發出去、不阻塞頁面、不管有沒有成功都不影響閱讀體驗。瀏覽器有一個專門為此設計的 API navigator.sendBeacon,它保證即使使用者馬上關頁面,這個請求也會在背景送完。模組二會用到它。
這個架構有一個取捨要先知道:beacon 只記得到「會執行 JS 的訪客」。爬蟲、關掉 JavaScript 的人、在 beacon 送出前就離開的人,都不會被算進去。因為紀錄的觸發點在瀏覽器端的 JS,client beacon 量到的數字偏保守——它少算,但不會多算。對個人 blog 想知道「哪篇文章有人看」這個目的,偏保守完全夠用;但如果哪天需要精確到不能漏一個請求(例如計費),就得回到有伺服器 access log 的架構。判斷訊號很簡單:漏算幾個訪客會不會造成實際損失——不會,就用 beacon;會,就別用。
下一步
beacon 該送到哪、那個接收端能承受多大的量、免費到什麼程度會開始要錢——這些是免費額度的思考方式與工具選型要回答的。想直接把 beacon 做出來,跳到模組二:流量 beacon 實作。