WordPress 瀏覽器快取與 Cache-Control 設定教學

速度測試工具跑完,分數卡在某個檻上不去,報告裡那行「Serve static assets with an efficient cache policy」(舊版叫 Leverage Browser Caching)大概是最常見的扣分項。它要說的其實很單純:你的網站沒告訴訪客的瀏覽器「這張 logo、這份 CSS 可以先存起來,下次別再重抓」,於是每個回訪者每翻一頁,都得把同樣的圖片和腳本重新下載一次。

WordPress 瀏覽器快取設定的核心,就是替靜態資源加上正確的 HTTP 回應標頭,其中最關鍵的是 Cache-Control。設定對了,回訪載入幾乎是瞬間完成,伺服器負擔也跟著降下來。多數教學會教你貼一段 .htaccess 程式碼了事,但真正容易出事的地方,反而在那段程式碼沒講到的環節——快取多久才安全、改版後訪客為什麼還看到舊樣式、WooCommerce 購物車頁能不能一起快取。這篇會把這些一次說清楚,從原理、Apache 與 Nginx 的實際設定,到驗證與避雷,給你一份能直接照做的完整流程。

瀏覽器快取到底在快取什麼

瀏覽器快取是把網站的靜態檔案,存在訪客自己電腦的硬碟上,下次回訪時直接從本機載入,不再向伺服器要一次。所謂靜態檔案,指的是內容短期內不會變的資源:圖片(.jpg、.png、.webp、.svg)、樣式表(.css)、JavaScript(.js)、字型(.woff2)、網站圖示(.ico)這幾類。

第一次造訪你的網站時,訪客的瀏覽器別無選擇,必須把每一個檔案都下載一遍才能把頁面畫出來。問題出在回訪與站內換頁。你的 logo 每一頁都出現,但它幾乎不會變;佈景主題的 CSS 也是整站共用。如果沒有快取指示,瀏覽器只能把這些一模一樣的檔案,在每次頁面載入時重新抓取,既浪費訪客的頻寬,也讓伺服器多扛一輪無謂的請求。

這裡要先釐清一個常被混為一談的概念。WordPress 圈子講「快取」時,多半指的是頁面快取(page cache),也就是把 PHP 動態產生的 HTML 結果存成靜態檔,省去每次都重跑一遍 WordPress 的成本——WP Super Cache、W3 Total Cache 主要在做這件事。瀏覽器快取是另一回事,它管的是「訪客端要不要把檔案存在本機」。再加上 CDN 快取(把檔案放到全球節點),三者各自獨立、可以疊加。本文聚焦的是其中的瀏覽器快取這一層。

Cache-Control 與 Expires 兩個標頭差在哪

控制瀏覽器快取的標頭有兩個,Cache-Control 與 Expires,至少要有一個存在,瀏覽器才知道該把資源存多久。兩者的差別在於描述「有效期」的方式不同。

Cache-Control
max-age=31536000
(存幾秒)

現代標準
Expires
Thu, 31 Dec 2026
(哪天過期)

舊式相容

Cache-Control 用「相對秒數」描述,例如 max-age=31536000 代表從請求那一刻起、這份資源可以重用一年(一年是 31,536,000 秒)。Expires 則是給一個「絕對的未來日期時間」,到那個時間點才算過期。

該選哪一個?Cache-Control 是較新的標準,也是現在的首選;兩者同時存在時,Cache-Control 會覆蓋 Expires。不過保留 Expires 也沒壞處,部分較舊的代理伺服器或像 GTmetrix 這類測速工具仍會檢查它。實務上常見的做法是兩個都寫,現代瀏覽器吃 Cache-Control,舊環境退而看 Expires。

常用的 max-age 秒數可以記幾個整數值,設定時直接代入:

快取期間 max-age 秒數
1 小時 3600
1 天 86400
1 週 604800
1 個月 2592000
1 年 31536000

Cache-Control 還有哪些常用指令

除了 max-age,Cache-Control 還能組合多個指令,用逗號分隔,分別控制「誰能快取」「要不要先驗證」。搞懂這幾個,才能在不同資源上下對策略,而不是整站套同一行。

public 與 private 這是一組對立指令。public 表示任何快取都能存,包括訪客瀏覽器與中間的 CDN;private 表示只允許訪客瀏覽器存,CDN 等共享快取不得儲存。帶有使用者個人資訊、又不希望被 CDN 快取的內容,就該標 private

no-cache 名字容易誤導。它不是「不要快取」,而是「可以存,但每次使用前都要先向伺服器驗證這份副本是否還有效」。真正完全不准存的是下一個。

no-store 這才是「完全不要快取」,瀏覽器與中間快取都不得儲存,每次都必須向伺服器重新索取。銀行帳務、結帳這類高度敏感的內容才該用它。

s-maxage 功能類似 max-age,但專門給共享快取(例如 CDN)看,可以單獨控制 CDN 端要快取多久,不影響訪客瀏覽器的設定。

immutable 這個指令多數教學沒提,但它正是讓你能安心把資源快取一年的關鍵。immutable 告訴瀏覽器:這個網址對應的檔案在有效期內絕對不會變,連使用者按重新整理時都不必回頭驗證。它專為「檔名帶版本指紋」的資源而設計,下一節會解釋為什麼這對 WordPress 特別重要。

stale-while-revalidate 允許快取在副本剛過期時,先把舊版本拿出來用,同時在背景靜靜抓一份新的回來。下一個訪客就會拿到更新後的版本。這讓「過期的瞬間」不必卡著等下載,體驗更順。

為什麼改版後訪客還看到舊樣式,該怎麼解

把 CSS 快取一年,最讓人擔心的就是這件事:你明明改好了佈景主題的樣式,自己看是新的,訪客那邊卻還是舊版面,因為他們的瀏覽器在快取期限內根本不會回來抓新檔。這不是 bug,正是快取在按你設定的規則運作。

WordPress 內建的解法藏在資源網址裡。你載入主題 CSS 時,網址通常長這樣:style.css?ver=1.4.2。後面那段 ?ver= 是版本查詢字串,當你更新主題、把版本號改成 1.4.3,整個網址就變了——對瀏覽器來說,這是一個它從沒見過的新網址,於是會重新下載,舊的快取自然失效。佈景主題與外掛若有規矩地透過 wp_enqueue_stylewp_enqueue_script 掛載資源並帶上版本號,這套機制就會自動生效。

懂了這層,immutable 的價值就清楚了。既然每次改版都會換網址,那麼「同一個網址的內容永遠不變」這個前提就成立,你大可放心把帶版本號的資源標成 Cache-Control: max-age=31536000, immutable,享受最長的快取期,又不會有訪客看到舊樣式的副作用。一年快取與即時改版,靠版本化網址同時拿到。

要提醒的是:手動上傳、自己用 FTP 丟進主題資料夾、檔名固定又沒帶版本號的圖片或檔案,不吃這套機制。這類資源若需要更新,記得改檔名,或在引用網址後面自己加上版本參數。

Apache 主機怎麼設定(.htaccess)

如果你的 WordPress 跑在 Apache 上(多數共享主機都是),設定方式是編輯網站根目錄的 .htaccess 檔。動手前務必先備份整個網站,.htaccess 寫錯一個字就可能讓整站噴 500 錯誤。

先確認自己是哪種伺服器:在瀏覽器打開網站,按右鍵選「檢查」,切到 Network(網路)分頁,重新整理頁面,點最上面的網域名稱那一列,在 Response Headers(回應標頭)裡找 server 欄位,會寫著 Apache 或 nginx。

確認是 Apache 後,把下面這段加進 .htaccess,位置放在 # BEGIN WordPress 之前或 # END WordPress 之後,不要插進 WordPress 自己管的那段中間。第一段用 mod_expires 設定有效期:

## 瀏覽器快取——有效期 ##
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresDefault "access plus 2 days"
</IfModule>

ExpiresByType 的好處是能按檔案類型分別設定——圖片與字型幾乎不變,設一年;CSS 與 JS 偶爾改版,設一個月;其餘沒指定的,預設兩天。第二段用 mod_headers 補上 Cache-Control,兩者並存讓現代與舊環境都涵蓋:

## 瀏覽器快取——Cache-Control ##
<IfModule mod_headers.c>
  <FilesMatch ".(ico|jpg|jpeg|png|gif|svg|webp|woff2)$">
    Header set Cache-Control "max-age=31536000, public, immutable"
  </FilesMatch>
  <FilesMatch ".(css|js)$">
    Header set Cache-Control "max-age=2592000, public"
  </FilesMatch>
</IfModule>

圖片與字型標上 immutable,配合前面說的版本化網址;CSS 與 JS 設一個月,因為改版較頻繁。存檔後重新跑一次測速工具,警告應該就消失了。

Nginx 主機怎麼設定

Nginx 上找不到 .htaccess,這類設定要寫進伺服器的設定檔(通常是 nginx.conf 或站台對應的 server block)。如果你用的是 WP Engine、SiteGround 這類代管主機,多半沒有權限改這個檔,正確做法是直接聯絡主機客服請他們替你開啟瀏覽器快取;多數代管與 CDN 環境其實預設就已經加好了標頭。

若你自管 VPS 或獨立主機,在對應的 server 區塊內加入以下 location 規則,按副檔名分別設定有效期與 Cache-Control:

location ~* .(jpg|jpeg|png|gif|svg|webp|ico|woff2)$ {
    expires 365d;
    add_header Cache-Control "public, immutable";
}
location ~* .(css|js)$ {
    expires 30d;
    add_header Cache-Control "public";
}

改完存檔後要重新載入 Nginx 設定才會生效,指令是 nginx -t 先測試語法、確認 OK 再 nginx -s reload。語法測試這一步別跳過,設定檔有錯時 reload 會失敗,先測能避免把線上服務弄掛。

不想碰程式碼,外掛能不能搞定

可以,而且對多數網站主來說,外掛是最務實的選擇。手動編輯設定檔的風險在於一旦寫錯就可能讓網站無法存取,外掛則是幫你把同樣的規則安全地寫進設定檔。

WP Rocket 啟用後會自動替你加上瀏覽器快取(它走的是 Expires 標頭那條路),不需要任何額外設定,這也是它常被推薦給新手的原因,缺點是付費授權。LiteSpeed Cache 則適合主機環境是 LiteSpeed 或 OpenLiteSpeed 的網站,它直接與伺服器整合,在外掛後台的瀏覽器快取開關打開,就會把最佳的 Cache-Control 與 Expires 規則寫進設定檔。其他像 W3 Total Cache、WP Fastest Cache 也都內建瀏覽器快取選項。SiteGround 主機的用戶則可以用免費的 SiteGround Optimizer。

選擇邏輯很單純:自己能安全編輯設定檔、想要最精細的控制,就手動寫;不想碰程式碼、或同時還需要頁面快取與壓縮等其他優化,就裝一個整合型快取外掛一次處理。手動與外掛不必二選一,但別讓兩邊同時寫進衝突的快取規則,否則排查問題時會很頭痛。

光設 max-age 還不夠:驗證標頭與 304 回應

到這裡解決的是「快取多久」,但快取還有另一半故事——副本過期之後該怎麼辦。多數教學講完 Cache-Control 就收尾,漏掉的正是這塊。

當資源的 max-age 到期,瀏覽器並不會傻傻地把整個檔案重抓一遍。它會帶著驗證標頭去問伺服器「我手上這份還能用嗎」。負責這件事的有兩個標頭。ETag 是伺服器替檔案算出的一段指紋字串,瀏覽器下次會用 If-None-Match 把這段指紋送回去比對。Last-Modified 則記錄檔案最後修改時間,瀏覽器用 If-Modified-Since 送回那個日期。兩者同時存在時,以 ETag 為準。

如果伺服器發現檔案沒變,就回一個 304 Not Modified,這個回應沒有檔案內容(body),只是一句「沒變,繼續用你手上那份」。瀏覽器於是直接沿用本機副本,省下整個檔案的下載量,只花了一個極小的往返請求。所以一套完整的快取策略是分工的:Cache-Control 決定有效期,期內完全不必連伺服器;期滿後由 ETag 或 Last-Modified 做快速驗證,沒變就回 304。理解這層,你在 DevTools 裡看到某個資源是 304 而不是 200,就知道快取正常運作,而不是以為它又重抓了。

WooCommerce 與動態頁面千萬別亂快取

前面幾段示範的 .htaccess 與 Nginx 規則,都鎖定圖片、CSS、JS、字型這些靜態檔案的副檔名,這是刻意的。HTML 頁面,尤其是 WordPress 的動態頁面,不能比照辦理設長效快取——這是不少教學把 html 也丟進 expires 清單時埋下的雷。

原因在於 WordPress 頁面是即時產生的。文章內容會更新、留言會增加,若把 HTML 也快取一年,訪客就會卡在某個舊版本看不到新內容。HTML 若真要快取,應該用很短的時間,或乾脆交給頁面快取外掛搭配它自己的清除機制處理。

WooCommerce 站更要警覺。購物車、結帳、我的帳戶、以及任何登入後才看得到的頁面,都帶有專屬於該訪客的資訊(購物車內容、收件資料、登入狀態)。這些頁面一旦被瀏覽器或 CDN 快取,最糟的情況是 A 訪客看到 B 訪客的購物車或地址。正確做法是讓這些路徑回傳 Cache-Control: no-storeprivate, no-cache,把它們明確排除在任何共享快取之外。主流的快取外掛預設就會把 WooCommerce 的這些頁面排除,這也是 WooCommerce 站建議用成熟外掛、而非全手寫規則的理由之一——手寫很容易漏掉某條動態路徑。

至於收款本身,瀏覽器快取設定不會也不該介入金流流程;你要做的只是確保結帳與帳戶相關頁面被標記為不快取,付款的處理交給金流外掛與其機制即可。

第三方腳本的警告為什麼修不掉

跑完測速後,常有人發現警告還在,而且指向的是 Google Analytics、Google Fonts 或 Facebook Pixel 這些根本不在自己伺服器上的檔案。這不是你設定錯了。這些檔案放在 Google、Meta 的伺服器上,快取期限由他們決定——Google Analytics 的腳本快取時間就偏短——你無權替別人的伺服器改標頭,所以工具照樣會標記它。

理論上的解法是把這些腳本下載下來、放到自己網站上,由你的伺服器套用前面那套標頭。但這條路的代價在維護:Google 一更新腳本或字型新增字元,你本機那份就過時了,得手動同步,否則等於在跑舊版程式碼,還可能有相容或安全的隱憂。對多數網站來說,這幾支小檔案在本地快取省下的時間,划不過長期維護的麻煩。比較務實的態度是:把自己伺服器上的所有資源快取設好,第三方那幾條警告就接受它,那不影響你網站本身的真實效能。

設定完怎麼確認真的生效了

改完設定,別只看測速分數有沒有變,直接驗證標頭最準。打開瀏覽器的開發者工具(Chrome 按 F12 或 Ctrl + Shift + I),切到 Network 分頁,重新整理頁面,點任一張圖片或 CSS 檔,在 Headers 裡的 Response Headers 找 Cache-Control 那一行,看看 max-agepublicimmutable 是不是你設的值。

更直接的看法是觀察回訪行為。在 Network 分頁裡留意每個資源的 Size(大小)欄位:如果顯示 (disk cache)(memory cache),代表瀏覽器根本沒連伺服器、直接從本機快取拿;如果顯示 304,代表它做了一次驗證、伺服器回說沒變。這兩種都是快取正常運作的訊號,只有顯示 200 並帶實際檔案大小,才是真的重新下載了。

也可以用指令列工具核對。在終端機下 curl -I https://你的網域/wp-content/themes/主題/style.css,回應裡會列出 Cache-ControlExpiresETag 等標頭,一眼就能確認伺服器到底送出了什麼。設好之後若測速工具還在報警告,先清掉網站快取、CDN 快取,等個一兩天讓設定傳播開來,再測一次,多半就過了。

把瀏覽器快取設對,不是為了讓測速工具的分數好看,而是讓每一個回訪的訪客都享受到接近瞬間的載入。真正穩健的設定,不外乎三件事拿捏到位:靜態資源(圖片、字型、CSS、JS)配長效快取與版本化網址,動態的 HTML 與 WooCommerce 私人頁面明確排除,再用 DevTools 與 curl 確認標頭如實送出。先在自管環境照本文的 Apache 或 Nginx 範例動手,或裝一個整合型快取外掛讓它替你寫規則,今天就把那行卡了很久的警告清掉。

相關文章
標籤: WordPress 效能, .htaccess, Cache-Control, 瀏覽器快取, WooCommerce 快取