愛你的快取 ❤️

使用者第二次載入網站時會使用 HTTP 快取,因此請確保快取運作正常。

這篇文章是愛上快取影片的延伸內容,該影片是 2020 年 Chrome 開發人員大會的延伸內容。請務必觀看這部影片:

使用者第二次載入網站時,瀏覽器會使用 HTTP 快取中的資源,加快載入速度。不過,網路快取標準可追溯至 1999 年,而且定義相當廣泛,因此要判斷 CSS 或圖片等檔案是否可從網路重新擷取,而非從快取中載入,這項作業的準確度就會降低。

在本文中,我將介紹合理且新穎的快取預設值,實際上「完全不快取」。但這只是預設值,當然比「關閉」更細微。快繼續往下閱讀吧!

目標

網站第 2 次載入時,您有兩個目標:

  1. 確保使用者能取得最新版本,如果您有任何變更,應能快速反映
  2. 執行 #1,同時盡量減少從網路擷取的資料

從廣義來說,您只想在客戶重新載入網站時,將最小的變更傳送給客戶。而且,要將網站架構得宜,確保能以最有效率的方式發布任何變更,這項挑戰相當艱鉅 (詳情請見下文和影片)。

不過,考量快取時,您還有其他調整選項,例如決定讓使用者的瀏覽器 HTTP 快取長時間保留您的網站,這樣一來,就不需要任何網路要求就能提供網站。或者,您已建構 Service Worker,可在檢查網站是否為最新版本之前,提供完全離線的網站服務。這是極端的做法,但確實有效,而且可用於許多離線優先應用程式類型的網頁體驗。不過,網頁不必採用只使用快取的極端做法,甚至不必完全採用只使用網路的極端做法。

背景

身為網頁開發人員,我們都習慣「快取過期」的概念。但我們幾乎本能地知道,有哪些工具可用來解決這個問題:執行「強制重新整理」或開啟無痕式視窗,或是使用瀏覽器開發人員工具的組合,清除網站資料。

一般網際網路使用者就沒有這麼幸運。因此,雖然我們有幾項核心目標,確保使用者在第 2 次載入時能獲得良好體驗,但也非常重視確保使用者不會遇到不良體驗或卡住的情況。(如果想聽我談論我們如何差點讓 web.dev/live 網站卡住,請觀看影片)

稍微說明一下背景資訊,「陳舊快取」的常見原因其實是 1999 年快取的預設值。這項技術仰賴 Last-Modified 標頭:

圖表:顯示使用者瀏覽器快取不同素材資源的時間長度
在不同時間產生的素材資源 (以灰色表示) 會在不同時間快取,因此第 2 次載入作業可取得快取和新鮮素材資源的組合

您載入的每個檔案都會保留目前生命週期的 10%,以便瀏覽器查看。舉例來說,如果 index.html 是在一個月前建立,瀏覽器會將其快取約三天。

這在當時是個好意,但考量到現今網站的高度整合性,這個預設行為可能會導致使用者擁有為不同網站版本設計的檔案 (例如星期二版本的 JS 和星期五版本的 CSS),因為這些檔案並未在同一時間更新。

光線充足的路徑

現代快取的預設做法是完全不快取,並使用CDN 將內容提供給使用者。每當使用者載入您的網站時,都會前往網路查看網站是否為最新版本。這項要求會由地理位置上靠近每位使用者的 CDN 提供,因此延遲時間較短。

您可以設定網路主機,以便透過此標頭回應網路要求:

Cache-Control: max-age=0,must-revalidate,public

這基本上表示檔案的有效期限為零,您必須透過網路驗證檔案,才能再次使用 (否則只是「建議」)。

就傳輸位元組而言,這個驗證程序的成本相對較低,如果大型圖片檔案未變更,瀏覽器會收到小型 304 回應,但這會產生延遲,因為使用者仍必須前往網路才能查看。這也是這種做法的主要缺點。對於在第一世界國家中使用快速連線,且所選 CDN 涵蓋範圍廣泛的使用者來說,這項功能非常實用,但對於使用較慢行動連線或基礎架構不佳的使用者來說,就沒有太大幫助。

無論如何,這都是一種新式做法,也是熱門 CDN Netlify 的預設做法,但幾乎所有 CDN 都可以設定這項做法。針對 Firebase 代管服務,您可以在 firebase.json 檔案的代管區段中加入這個標頭:

"headers": [
  // Be sure to put this last, to not override other headers
  {
    "source": "**",
    "headers": [ {
      "key": "Cache-Control",
      "value": "max-age=0,must-revalidate,public"
    }
  }
]

因此,雖然我仍建議採用這個做為合理的預設值,但這只是預設值!請繼續閱讀,瞭解如何介入並升級預設值。

指紋網址

在網站上提供的素材資源、圖片等名稱中加入檔案內容的雜湊,即可確保這些檔案一律含有獨特的內容,例如檔案名稱為 sitecode.af12de.js。當伺服器回應這些檔案的要求時,您可以使用這個標頭設定瀏覽器,安全地指示終端使用者瀏覽器將這些檔案快取一段長時間:

Cache-Control: max-age=31536000,immutable

這個值以秒為單位。根據規格,這實際上等同於「永久」。

重要的是,請勿手動產生這些雜湊,因為這需要太多手動作業!您可以使用 Webpack、Rollup 等工具來處理這個問題。請務必參閱工具報告,進一步瞭解這些指標。

請注意,除了 JavaScript 之外,圖示、CSS 和其他不可變動的資料檔案等資產,也可以使用這種方式命名。(請務必觀看上述影片,進一步瞭解程式碼分割作業,這樣您在網站變更時,就能傳送更少的程式碼)。

無論您的網站如何快取,這類指紋檔案對您可能建立的任何網站都非常有用。大多數網站並不會在每次發布時變更。

當然,我們無法以這種方式重新命名「友善」的使用者介面網頁:將 index.html 檔案重新命名為 index.abcd12.html,這麼做是不切實際的,因為您無法要求使用者每次載入網站時都前往新的網址!這些「友善」網址無法以這種方式重新命名及快取,這會讓我找到可能的折衷做法。

中間地帶

在快取方面,顯然有中庸之道。我提出了兩種極端的做法:永遠不快取或永遠快取。您可能會想將許多檔案暫時快取,例如我前面提到的「友善」網址。

如果您想快取這些「友善」網址及其 HTML,建議您考量這些網址包含哪些依附元件、這些網址如何快取,以及快取這些網址一段時間可能對您造成的影響。我們來看看包含以下圖片的 HTML 頁面:

<img src="/images/foo.jpeg" loading="lazy" />

如果您透過刪除或變更此延遲載入圖片來更新或變更網站,查看快取 HTML 版本的使用者可能會看到錯誤或缺少圖片,因為他們在重新造訪網站時,仍會快取原始 /images/foo.jpeg

只要你小心操作,這項政策可能不會對你造成影響。不過,請務必記住,當使用者快取網站時,網站就不再只存在於伺服器上。而是可能存在於使用者瀏覽器快取的片段中。

一般來說,大多數快取指南都會提到這類設定,例如您要快取一小時、數小時等等。如要設定這類快取,請使用類似以下的標頭 (快取時間為 3600 秒,即一小時):

Cache-Control: max-age=3600,immutable,public

最後一點是,如果您正在製作時效性內容,使用者通常只會存取一次 (例如新聞文章),我認為這些內容不應快取,您應使用上述合理的預設值。我認為,我們經常高估快取的價值,而忽略使用者希望一律看到最新、最棒的內容,例如新聞報導或當前事件的重要更新。

非 HTML 選項

除了 HTML 之外,其他位於中間位置的檔案選項包括:

  • 一般來說,請尋找不會影響其他人的資產

    • 例如:避免使用 CSS,因為這會導致 HTML 呈現方式發生變更
  • 用於時事文章的大型圖片

    • 使用者可能不會多次造訪任何一篇文章,因此請勿永久快取相片或主圖片,以免浪費儲存空間
  • 代表本身具有生命週期的資產

    • 天氣相關的 JSON 資料可能每小時才發布一次,因此您可以將先前的結果快取一小時,因為在您指定的時間範圍內,這項資料不會變更
    • 開放原始碼專案的建構作業可能會受到速率限制,因此請快取建構狀態圖片,直到狀態可能變更為止

摘要

當使用者第二次載入網站時,就代表他們對您有信心,並且想回訪網站,進一步瞭解您提供的服務。此時,您不只要縮短載入時間,還可以使用多種選項,確保瀏覽器只執行必要作業,以提供快速且最新的使用體驗。

快取並非網頁上的新概念,但可能需要合理的預設值。建議您在需要時使用預設值,並強烈選擇加入更完善的快取策略。感謝您閱讀本信!

另請參閱

如需 HTTP 快取的一般指南,請參閱「透過 HTTP 快取防止不必要的網路要求」。