裝了全頁快取,網站速度立刻有感,TTFB 從幾百毫秒掉到幾十毫秒。問題是過幾天就有人回報:明明登入了卻顯示登出狀態、加進購物車的商品結帳前消失、後台改了內容前台卻不更新、跑 A/B 測試所有人都看到同一個版本。這些症狀看起來毫無關聯,根因卻幾乎都是同一件事——該被排除在快取之外的動態內容,被快取當成靜態頁面存了下來,再原封不動端給下一個人。
WordPress 快取排除規則就是用來劃清這條界線:哪些頁面、哪些請求、帶哪些 cookie 的訪客,必須繞過快取直接由原始伺服器產生回應。設定對了,匿名訪客吃快取、登入者與購物中的客人吃即時內容,兩邊互不干擾。設定錯了,輕則功能怪異,重則把某個會員的購物車或帳號頁面端給陌生人,變成資料外洩。這篇會把登入狀態、WooCommerce 購物車、A/B 測試這三類最容易出事的動態內容拆開講清楚,並給一套先判斷「這到底是不是快取造成的」的排查順序。
全頁快取為什麼會跟動態內容打架
全頁快取的運作前提是「同一個 URL,回應對所有人都一樣」。它把 WordPress 第一次產生的 HTML 存成一份靜態檔,後續相同請求直接回這份檔,不再讓 PHP 跑、不再查資料庫。對部落格文章、商品介紹、關於我們這種對誰都長一樣的頁面,這個假設成立,速度提升也最明顯。
衝突發生在內容「因人而異」的時候。WordPress 本質上是資料庫驅動的動態系統,每個請求都可能依登入身分、購物車內容、測試分組產生不同的 HTML。一旦這種頁面被存進全頁快取,第一個訪客看到的版本就會被鎖定,端給後面所有人。登入者看到的是匿名版(顯示未登入)、A 組訪客看到的是 B 組的測試版本、甚至某人的私人帳號資料被快取後曝光給他人。
可以把內容分成三類來判斷該不該排除:
- 靜態內容:對所有訪客都相同,更新頻率低,例如文章正文、商品說明、CSS 與圖片。這類盡量快取,是效能的主要來源。
- 動態內容:依使用者狀態即時變化,例如購物車、我的帳號、會員專屬區塊。這類必須排除。
- 事件驅動內容:介於兩者之間,例如庫存數量、限時優惠倒數、留言數。短時間內對多數人相同,但會隨事件變動。實務上多半併入動態處理,或用較短的快取時間加上主動清快取來折衷。
判斷準則只有一條:這個回應的 HTML 會不會因為「誰在看」而不同。會,就要排除或改用其他技術處理;不會,就放心快取。
排查前先確認問題到底出在哪一層快取
動手改排除規則之前,要先確定問題真的是快取造成的,以及是哪一層快取造成的。WordPress 站台常見三層快取疊在一起,每一層都有自己的排除設定,改錯地方等於沒改。
- 外掛層全頁快取:WP Rocket、LiteSpeed Cache、W3 Total Cache、WP Super Cache 這類,在 WordPress 內部把頁面存成 HTML 檔。
- 伺服器層快取:NGINX FastCGI Cache、Varnish 這類反向代理快取,位於 WordPress 之前,外掛的排除設定管不到它。
- CDN 邊緣快取:Cloudflare、Fastly 這類,把頁面快取在離訪客最近的節點,比伺服器層又更外面一層。
判斷層級的最快方法是看回應標頭。多數快取會在 HTTP 回應裡加一個狀態標頭,告訴你這次請求是怎麼被處理的。常見值有幾種:HIT 代表直接由快取回應、MISS 代表這次沒命中但會被存起來供下次使用、BYPASS 代表被排除規則擋下沒走快取、STALE 代表回了一份過期內容同時在背景重新產生。SpinupWP 的 FastCGI 快取用 Fastcgi-Cache 標頭,Cloudflare 用 cf-cache-status,各家名稱不同但邏輯一致。
排查動態內容問題時的判斷順序如下:
第一、用無痕視窗與一般登入視窗對照。登入視窗看到的頁面如果正常、無痕視窗壞掉,多半是快取把匿名版錯誤套用;反過來登入視窗壞掉,通常是登入請求被快取了。
第二、檢查回應標頭的快取狀態值。出問題的那個 URL(例如購物車頁)如果回 HIT,代表它被快取了而它根本不該被快取——這就鎖定了問題是排除規則漏設。
第三、逐層關閉測試。從最外層的 CDN 開始,暫時把該頁設成不快取,重測;還是壞就往內推到伺服器層,再到外掛層。哪一層關掉後問題消失,排除規則就該補在那一層。
第四、確認 cookie 有沒有正確帶上。用瀏覽器開發者工具的 Application 分頁看 Storage 區,登入後應該要有 wordpress_logged_in_ 開頭的 cookie、加入購物車後應該要有 WooCommerce 的 session cookie。cookie 沒出現,排除規則自然無從觸發。
登入使用者怎麼從快取裡排除
登入者一律繞過全頁快取,這是最安全也最省事的做法。原因是登入後的頁面幾乎處處因人而異——管理列、個人化問候、後台連結、會員等級顯示,全都不該被快取後端給別人。與其逐頁維護「哪些頁面登入時要排除」的清單,不如以「只要是登入者就整站繞過」一刀切。
WordPress 登入後會在瀏覽器寫入幾個固定前綴的 cookie,快取層就是靠偵測這些 cookie 來判斷訪客是否登入:
wordpress_logged_in_:登入狀態的主要判斷依據,後面接一段雜湊。wordpress_sec_:在 HTTPS 連線下使用的安全驗證 cookie。wp-settings-:記錄後台介面偏好設定。comment_author_:近期留過言的訪客,也常被一併排除避免顯示錯誤的留言者資訊。
設定原則是:請求只要帶上以 wordpress_logged_in_ 開頭的 cookie,就標記為繞過快取。多數快取外掛預設已經內建這條規則,不必手動加。需要手動處理的場景是兩種:一是你用了非 WordPress 原生的登入機制(例如第三方會員系統、單一登入),它寫的 cookie 不在預設清單裡,必須自己把那個 cookie 名稱加進排除清單;二是 CDN 層——CDN 不認識 WordPress 的 cookie,得在 CDN 的快取規則裡明確設定「偵測到這些 cookie 就 bypass」,否則外掛層擋住了、CDN 層卻照樣把匿名版快取下來。
在 NGINX FastCGI 設定裡,這條規則長這樣:
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
要留意一個常被忽略的反效果:如果你的網站讓大量訪客都處於登入狀態(例如社群型、會員型網站),整站對登入者繞過快取,等於這群人全部回到「每次請求都讓 PHP 跑、都查資料庫」的狀態。快取命中率會明顯下降,原始伺服器負載上升。WordPress VIP 的文件特別提醒,繞過快取的請求一定會打到原始伺服器產生直接的 SQL 查詢,流量尖峰時大量這種請求可能拖垮主資料庫、回出 503。會員量大的站台,這時要靠物件快取(如 Redis)把資料庫查詢結果快取起來,分擔全頁快取繞過後的壓力。
WooCommerce 購物車與結帳怎麼排除
電商站最容易出事的就是購物車、結帳、我的帳號這三類頁面,它們的內容完全綁定當前訪客的 session,被快取後果最直接——一個客人看到另一個客人的購物車或訂單。處理方式分成「依路徑排除」與「依 cookie 排除」兩道,兩道都要做才完整。
依路徑排除是擋掉固定的功能頁。多數快取方案預設已經把這幾個路徑排除,但自訂或換過固定連結結構的站要自己確認:
/cart/(購物車)/checkout/(結帳)/my-account/(我的帳號,含底下所有子頁如訂單、設定)
路徑排除通常會連帶排除巢狀子路徑,例如設定排除 /my-account/ 後,/my-account/orders/ 與 /my-account/settings/ 也會一起被擋。
依 cookie 排除則是擋掉「人在購物中但不在上述頁面」的情況。一個客人把商品加進購物車後,可能繼續逛商品列表頁——那是個會被快取的一般頁面,但頁面上的迷你購物車(cart widget)顯示的件數必須是即時的。這時就要靠 WooCommerce 寫入的 session cookie 來判斷:
woocommerce_cart_hash:購物車內容的雜湊值。woocommerce_items_in_cart:購物車內的商品數量。wp_woocommerce_session_:WooCommerce 的 session 識別 cookie。
請求只要帶上這些 cookie,代表訪客有進行中的購物 session,該繞過快取以確保購物車資訊正確。在 NGINX 上可以直接用路徑條件處理功能頁:
if ($request_uri ~* "/(cart|checkout|my-account)/*$") {
set $skip_cache 1;
}
不過整站對「購物車非空」的訪客繞過快取,又會掉回前面講的命中率問題。比較進階的折衷是讓功能頁照常排除,但商品列表這類「靜態為主、只有迷你購物車是動態」的頁面仍然快取,改用 AJAX 在頁面載入後即時抓購物車件數填回去。WooCommerce 本身就提供把購物車元件 ajax 化的做法,這樣靜態骨架吃快取、動態片段走 JavaScript,兩者兼得。這種把頁面拆成靜態與動態片段分別處理的概念,就是片段快取(fragment caching)的核心思路。
收款流程牽涉到結帳頁與金流回呼,這些頁面與其對應的回呼網址(例如付款結果通知)一律不能快取,否則交易狀態會被舊資料覆蓋。設定排除時記得把金流外掛產生的回呼路徑也納入,但這屬於各金流外掛文件該交代的範圍,排除規則上把它當成「動態、不可快取」處理即可。
A/B 測試為什麼整站只看到同一個版本
A/B 測試與全頁快取天生互斥:測試的目的是讓不同訪客看到不同變體,全頁快取的目的卻是讓所有訪客看到同一份快取頁。沒處理好的結果就是——第一個訪客落在哪個變體,那份頁面就被快取下來,後面所有人都看到同一個變體,測試數據完全失真。
解法有兩種主流路線,差別在於「用什麼當快取的區分鍵」。
第一種是查詢參數法(query argument)。測試工具替每個訪客隨機指派一個變體,並把變體編號塞進網址的查詢參數,例如 ?nab=0 是變體 A、?nab=1 是變體 B。因為網址不同,幾乎所有快取系統都會把它們當成不同頁面分別快取,互不覆蓋。代價是訪客落地時要做一次 JavaScript 轉址才能到帶參數的網址,比直接給內容稍慢。這種做法的設定關鍵在於:確認你的快取外掛沒有把這個查詢參數當成「可忽略的追蹤參數」剝掉。像 utm、gclid、fbclid 這類追蹤參數,快取通常設定為忽略(不納入快取鍵),這樣帶不同 utm 的同一頁仍共用一份快取;但測試用的 nab 參數絕對不能被忽略,否則變體又會混在一起。
第二種是 cookie 動態快取法(cache salting)。訪客被指派變體後,編號存進一個 cookie(例如 nabAlternative),快取系統依這個 cookie 的值產生多份快取版本——同一個 URL,cookie 值不同就存不同份。好處是不必 JavaScript 轉址,落地直接給對的內容、速度較快。代價是設定較繁瑣,且這種「依 cookie 分版」會讓同一頁的快取碎裂成多份。這裡有一條重要原則:如果某個 cookie 並不會實際改變匿名訪客看到的 HTML,就絕對不要把它放進快取鍵,否則快取會被無謂地切成大量碎片,命中率崩落。只有真正影響頁面內容的 cookie(如測試分組)才該納入。
兩種做法共通的一個動作最容易被漏掉:測試啟動或停止時要清空快取。否則快取裡可能殘留著「測試已結束、頁面卻還是舊變體」或「測試剛開始、快取卻還是測試前版本」的不一致狀態。設定排除規則的同時,把「測試狀態變更時觸發清快取」一併設好,才算完整。
路徑、查詢參數、nonce 還有哪些細節要排除
除了登入、購物車、A/B 測試這三大類,還有幾個系統層級的排除是每個站都該設好的基礎,漏掉同樣會出問題。
必須永遠排除的路徑有幾條,理由各不相同:
/wp-admin/:後台。被快取會導致編輯看到舊內容、設定存不進去、管理功能默默失效。wp-login.php:登入頁。被快取可能把某人的登入狀態或 session token 端給別人。/wp-admin/admin-ajax.php:AJAX 端點。表單、即時搜尋、篩選等動態操作都走這裡,快取它會回出錯誤結果。/wp-json/:REST API。多半回傳動態甚至因人而異的資料,除非你清楚某個端點安全可快取並設好了快取鍵,否則整段排除。xmlrpc.php、/feed/、sitemap.xml:這類由系統動態產生或不適合快取的端點。
查詢字串的處理要拿捏。預設上,帶查詢參數的網址多半不被快取,因為參數常代表動態請求。但純追蹤用的參數(utm 系列、gclid、fbclid、mc_cid 等)並不改變頁面內容,若因為它們而每個來源連結都快取一份,等於白白浪費快取。正確做法是把這些追蹤參數設為「忽略」——從快取鍵裡剔除,讓帶不同追蹤參數的同一頁共用一份快取。設定排除時要分清楚:會改變內容的參數(如搜尋關鍵字、分頁)保持不快取,不改變內容的追蹤參數則設為忽略以提升命中率。
nonce 過期是全頁快取最隱蔽的衝突之一。WordPress 用 nonce(一次性安全碼)防止跨站請求偽造,表單送出、AJAX 操作都會驗證它。問題是 nonce 有時效,全頁快取卻把含著某個 nonce 的頁面整份存了下來——等快取頁被端出去時,那個 nonce 早就過期,使用者送出表單就會收到驗證失敗。常見症狀是登入或留言時跳出「cookie 驗證失敗」「nonce 無效」。處理方向是不要讓帶 nonce 的頁面被長時間快取,或改用能「在快取頁上替 nonce 打洞」的機制——LiteSpeed Cache 之類支援 ESI(Edge Side Includes)的方案,可以把 nonce 這種動態片段獨立快取、與整頁的快取時效脫鉤,每次都給新鮮的 nonce。
設定這些排除時,反過來也要警惕排除過頭。把太多頁面塞進「永不快取」清單,快取命中率會下滑,繞過的請求全打到原始伺服器,等於慢慢把快取的效益抵銷掉。每加一條排除前先問:這個頁面或請求真的會因人而異嗎?不會的話,它該被快取,而不是被排除。
排除規則設定完之後,怎麼確認真的有生效
把規則設好不等於設對,最後一定要實際驗證。前面排查用的工具,這時反過來當驗證工具用。
逐項對照確認:登入後開無痕視窗瀏覽,前台應顯示未登入而登入視窗顯示已登入,兩者不互相污染;購物車頁、結帳頁、我的帳號頁的回應標頭應該回 BYPASS 而非 HIT;加入商品後迷你購物車件數即時更新;A/B 測試在不同瀏覽器或裝置看到的變體不全相同,且測試工具後台能正常累積各變體數據;後台改文章並清快取後,前台確實更新。
如果站台疊了多層快取,每一層都要分別驗一次回應標頭——外掛層擋住了,不代表 CDN 層也擋住了。動態內容的排除規則必須在最外層真正面對訪客的那層也設好,整條鏈路才算密合。
WordPress 快取排除規則的本質不是「設一份越長越好的黑名單」,而是精準劃出靜態與動態的界線:該快取的盡量快取以換取速度,該繞過的精準繞過以保住正確性與安全。先用回應標頭與無痕對照判斷問題出在哪一層,再依登入 cookie、WooCommerce session cookie、測試參數三條主線把動態內容擋在快取之外,最後逐層驗證生效。把這套順序走過一遍,速度與功能就不必二選一。