愛你的快取 ❤️

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

本文將隨附於 2020 年 Chrome 開發人員高峰會「延伸內容」的「愛你的快取」影片。請務必觀看這部影片:

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

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

目標

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

  1. 請確保使用者取得了最新版本;如果您變更了某些內容,應該很快就能得到更新。
  2. 執行 #1,同時盡量減少從網路擷取的資料

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

不過,考量快取時,您也能使用其他調整鈕,例如決定讓使用者的瀏覽器 HTTP 快取長時間保留您的網站,這樣一來,就不需要任何網路要求就能提供網站。或是建構了服務工作處理程序,將網站完全離線,再檢查網站是否為最新版本。這是相當極端的選項,適用於許多類似離線優先的應用程式網頁體驗,但網站不必受限於僅快取的極限,甚至完全沒有網路。

背景

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

網路上的一般使用者沒有如此奢華,因此,雖然我們有幾項核心目標,確保使用者在第 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 快取防止不必要的網路要求」。