往返快取

往返快取 (或 Bfcache) 是瀏覽器最佳化功能,可提供即時往返瀏覽。進而大幅改善瀏覽體驗,尤其對網路速度較慢或裝置的使用者而言更是如此。

本頁面概述如何針對所有瀏覽器進行網頁快取最佳化

瀏覽器相容性

電腦和行動裝置的 FirefoxSafari 已支援 bfcache。

自第 86 版起,Chrome 已為少數使用者啟用 Android 上的跨網站導覽功能。在後續版本中,系統會逐步推出額外支援。自 96 版起,電腦和行動裝置的所有 Chrome 使用者都會啟用 bfcache。

bfcache 基本概念

bfcache 是記憶體內的快取,會在使用者離開時儲存網頁的完整快照。將整個網頁保留在記憶體中,如果使用者決定返回網頁,瀏覽器可快速將其還原,無需重複所有載入頁面所需的網路要求。

下列影片只是示範 bfcache 加速瀏覽速度:

使用 bfcache 可以加快往返瀏覽作業的速度。

Chrome 使用資料顯示,在電腦上瀏覽 10 次時,每 10 次瀏覽中就有 1 次是往返網頁。因此,bfcache 可以節省大量的時間和數據用量。

「快取」的運作方式

bfcache 使用的「快取」與 HTTP 快取不同,後者在加快重複瀏覽速度方面扮演的角色。bfcache 是記憶體中整個網頁的快照,包括 JavaScript 堆積,而 HTTP 快取僅包含先前所發出要求的回應。所有要求載入網頁且可透過 HTTP 快取完整執行的要求很少發生,因此使用 bfcache 還原的重複造訪速度會比最佳的未最佳化非 bfcache 瀏覽的速度更快。

不過,在記憶體中建立頁面的快照,會牽涉到如何保留處理中程式碼的最佳方式。舉例來說,當網頁位於 Bfcache 時,要如何處理超過逾時的 setTimeout() 呼叫?

解決方法是,瀏覽器會暫停所有待處理的計時器,或對 bfcache 中網頁的未解決承諾 (包括 JavaScript 工作佇列中幾乎所有待處理工作),並且從 bfcache 恢復網頁繼續處理工作。

在某些情況下 (例如逾時和承諾),這個風險就相當低,但在其他情況下則可能造成混淆或非預期的行為。舉例來說,如果瀏覽器暫停索引資料庫交易的必要工作,可能會影響相同來源中其他開啟的分頁,因為相同的索引資料庫資料庫可同時透過多個分頁存取。因此,瀏覽器通常不會嘗試在 IndexedDB 交易過程中快取網頁,也不會嘗試使用可能影響其他網頁的 API 時。

如要進一步瞭解 API 用量如何影響網頁的 bfcache 適用資格,請參閱「針對 bfcache 改善網頁」。

bfcache 與單頁應用程式 (SPA)

bfcache 可與瀏覽器管理的導覽搭配運作,因此無法用於單頁應用程式 (SPA) 內的「軟導覽」。不過,bfcache 可在離開及返回 SPA 時提供協助。

用來觀察 bfcache 的 API

雖然 bfcache 是瀏覽器會自動執行的最佳化作業,但開發人員仍必須瞭解網頁發生的時間,以便據此為網頁進行最佳化調整,並據此調整任何指標或效能測量方式

用於觀察 bfcache 的主要事件是頁面轉換作業 pageshowpagehide多數瀏覽器都支援這類事件。

頁面進入或離開 bfcache 後也會調派較新的 Page Lifecycle 事件 (freezeresume),以及其他在某些情況下 (例如背景分頁凍結以盡量減少 CPU 用量)。這些事件僅適用於以 Chromium 為基礎的瀏覽器。

觀察網頁何時透過 Bfcache 還原

頁面初次載入,以及任何網頁透過 bfcache 還原時,都會在 load 事件之後觸發 pageshow 事件。pageshow 事件包含 persisted 屬性,如果網頁是從 bfcache 和 false 還原,則為 true。您可以使用 persisted 屬性來區分一般網頁載入和 bfcache 還原作業。例如:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('This page was restored from the bfcache.');
  } else {
    console.log('This page was loaded normally.');
  }
});

在支援 Page Lifecycle API 的瀏覽器中,當使用者從 bfcache 還原頁面時 (緊接在 pageshow 事件之前),以及當使用者再次造訪凍結的背景分頁時,就會觸發 resume 事件。如果您想在頁面凍結後更新網頁狀態 (包括 bfcache 中的網頁),可以使用 resume 事件,但如果想評估網站的 bfcache 命中率,必須使用 pageshow 事件。在某些情況下,您可能會需要同時使用兩者。

如要進一步瞭解 bfcache 評估最佳做法,請參閱 bfcache 對數據分析和效能評估的影響

觀察網頁何時進入 bfcache

當網頁卸載,或是瀏覽器嘗試將其放入 bfcache 時,會觸發 pagehide 事件。

pagehide 事件也具有 persisted 屬性。如果是 false,就能確保網頁不會進入 bfcache。不過,persistedtrue 並不保證一定會快取網頁。這表示瀏覽器「預期」intends會快取網頁,但可能還有其他因素導致網頁無法快取。

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    console.log('This page *might* be entering the bfcache.');
  } else {
    console.log('This page will unload normally and be discarded.');
  }
});

同樣地,如果 persistedtrue,則會在 pagehide 事件之後立即觸發 freeze 事件,但這只是表示瀏覽器「希望」intends快取網頁。稍後仍可能基於一些原因而捨棄它。

針對網頁快取進行網頁最佳化

並非所有網頁都會儲存在 bfcache 中,即使網頁已經儲存在快取中,也不會無限期保留。以下頁面概略說明何謂網頁快取,以及讓瀏覽器更容易快取網頁的最佳做法,並提高快取命中率。

pagehide 取代 unload

如要在所有瀏覽器中針對 bfcache 進行最佳化,最重要的方法是一律不使用 unload 事件監聽器。請改為監聽 pagehide,因為這個項目會在網頁進入 bfcache 和 unload 時同時觸發。

unload 是較舊的功能,原本設計會在使用者離開頁面時觸發。這已不再適用,但許多網頁仍會假設瀏覽器以這種方式使用 unload 運作,且在 unload 觸發後,已卸載的網頁就會停止運作。如果瀏覽器嘗試快取已載入的網頁,就可能破壞 bfcache。

在電腦版 Chrome 和 Firefox 中,含有 unload 事件監聽器的網頁將無法採用 bfcache 的做法,這麼做可降低風險,同時也會導致系統無法快取大量網頁,進而使重新載入速度變慢。Safari 會嘗試使用 unload 事件監聽器快取某些網頁,但為了減少潛在的中斷情形,不會在使用者離開時執行 unload 事件,導致 unload 事件監聽器變得不可靠。

在行動裝置上,Chrome 和 Safari 會嘗試使用 unload 事件監聽器快取網頁,因為 unload 在行動裝置上不穩定,會導致破壞風險降低。行動版 Firefox 會將使用 unload 的網頁視為不適用 Bfcache;iOS 版本要求所有瀏覽器都必須使用 WebKit 轉譯引擎,因此運作方式與 Safari 相同。

如要判斷網頁上的任何 JavaScript 是否使用 unload,建議您使用 Lighthouse 中的 no-unload-listeners 稽核

如要瞭解 Chrome 的淘汰 unload 計畫,請參閱「淘汰卸載事件」。

使用權限政策避免在網頁上使用卸載處理常式

部分第三方指令碼和擴充功能可以為網頁新增卸載處理常式,使其不符合 Bfcache 的執行速度,藉此減緩網站速度。如要在 Chrome 115 以上版本中防止這種情況發生,請使用權限政策

Permission-Policy: unload()

只在有條件的情況下新增 beforeunload 事件監聽器

beforeunload 事件不會讓網頁不適用往返快取。然而,這個做法並不可靠,因此建議您在絕對必要的情況下才使用。

我們舉例說明 beforeunload 的用途,是要警告使用者,當他們離開頁面時,他們所做的變更將會遺失。在此情況下,我們建議只有在使用者有未儲存的變更時,才新增 beforeunload 事件監聽器,然後在儲存的變更儲存後立即移除,如以下程式碼所示:

function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});

盡量避免使用 Cache-Control: no-store

Cache-Control: no-store 是可在回應上設定的 HTTP 標頭網路伺服器,指示瀏覽器不要將回應儲存在任何 HTTP 快取中。用於含有敏感使用者資訊的資源,例如需要登入的網頁。

雖然 bfcache 不是 HTTP 快取,但在網頁資源上設定 Cache-Control: no-store (但不在任何子資源上) 時,瀏覽器之前已經從 bfcache 中排除網頁。Chrome 會努力變更這項行為,同時維護使用者隱私,但根據預設,使用 Cache-Control: no-store 的網頁不適用往返快取。

為針對 bfcache 進行最佳化,請只在含有無法快取的機密資訊的網頁上使用 Cache-Control: no-store

如果網頁想隨時提供最新內容,但不包含機密資訊,請使用 Cache-Control: no-cacheCache-Control: max-age=0。這些參數會指示瀏覽器在放送內容前重新驗證內容,而且不會影響網頁是否符合資格,因為從 bfcache 還原網頁不會與 HTTP 快取有關。

如果您的內容每分鐘會變動,請改用 pageshow 事件擷取更新,以便按照下一節所述的方式更新網頁。

在 bfcache 還原後更新過時或機密資料

如果您的網站會保留使用者狀態資料,特別是當當中含有敏感的使用者資訊時,您必須在從 Bfcache 還原網頁後更新或清除這些資訊。

舉例來說,如果使用者在公用電腦上從某個網站登出網站,而下一位使用者點選返回按鈕時,bfcache 中的過時資料可能包括第一個使用者登出後應清除的私人資料。

為了避免發生這類情況,如果 event.persistedtrue,請一律在 pageshow 事件之後更新頁面:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Do any checks and updates to the page
  }
});

針對部分變更,建議您改為強製完整重新載入,並保留轉送導覽的瀏覽記錄。下列程式碼會檢查 pageshow 事件中是否有網站專屬的 Cookie,並在沒有找到該 Cookie 時重新載入:

window.addEventListener('pageshow', (event) => {
  if (event.persisted && !document.cookie.match(/my-cookie)) {
    // Force a reload if the user has logged out.
    location.reload();
  }
});

廣告和 bfcache 還原

您可能會想避免使用 bfcache,這樣網頁才能在每個返回或向前瀏覽時放送一組新的廣告。不過,這會造成網站效能不佳,且不會持續提升廣告參與度。舉例來說,使用者可能想返回頁面點選廣告,但如果網頁重新載入,而不是從 bfcache 還原,可能會顯示其他廣告。建議您透過 A/B 測試找出最適合網頁的策略。

如果網站想要重新整理 bfcache 還原作業上的廣告,您可以在 event.persistedtrue 時只重新整理 pageshow 事件的廣告,而不會影響網頁效能,如此 Google 發布商代碼範例所示。 如要進一步瞭解您的網站最佳做法,請洽詢您的廣告技術供應商。

避免使用 window.opener 個參照

在舊版瀏覽器中,如果透過含有 target=_blank 的連結使用 window.open() 開啟網頁,而沒有指定 rel="noopener",開啟頁面會參照開啟網頁的視窗物件。

除了會有安全性風險之外,含有非空值 window.opener 參照的頁面也不應安全放入 bfcache,因為這麼做可能會破壞任何試圖存取該網頁的網頁。

為了避免發生這些風險,請使用 rel="noopener" 避免建立 window.opener 參照。這是所有新版瀏覽器的預設行為。 如果您的網站需要開啟視窗並使用 window.postMessage() 或直接參照視窗物件來控制,則開啟的視窗和開啟器都不適用 bfcache。

在使用者離開前關閉公開連線

如前文所述,將網頁放入 bfcache 時,系統會暫停所有排定執行的 JavaScript 工作,並在網頁移出快取後恢復執行這些工作。

如果這些排定的 JavaScript 工作只能存取 DOM API,或不屬於目前頁面的其他 API,暫停這些工作便不會在使用者無法看見網頁的情況下暫停。

不過,如果這些工作已連結至可透過相同來源的其他頁面 (例如 IndexedDB、Web Lock 和 WebSocket) 存取的 API,則暫停這些工作會阻止這些網頁上的程式碼執行,從而破壞這些頁面。

因此,如果網頁含有下列任一種內容,部分瀏覽器就不會嘗試將網頁嵌入 bfcache:

如果您的網頁正在使用上述任何 API,強烈建議您在 pagehidefreeze 事件期間關閉連線,並移除或中斷連線觀察器。這可讓瀏覽器安全快取頁面,以免影響其他開啟的分頁。接著,如果網頁是透過 bfcache 還原,您可以在 pageshowresume 事件期間重新開啟或重新連結這些 API。

以下範例說明如何在 pagehide 事件監聽器中關閉開放式連線,確保使用 IndexedDB 的網頁符合 bfcache 的資格:

let dbPromise;
function openDB() {
  if (!dbPromise) {
    dbPromise = new Promise((resolve, reject) => {
      const req = indexedDB.open('my-db', 1);
      req.onupgradeneeded = () => req.result.createObjectStore('keyval');
      req.onerror = () => reject(req.error);
      req.onsuccess = () => resolve(req.result);
    });
  }
  return dbPromise;
}

// Close the connection to the database when the user leaves.
window.addEventListener('pagehide', () => {
  if (dbPromise) {
    dbPromise.then(db => db.close());
    dbPromise = null;
  }
});

// Open the connection when the page is loaded or restored from bfcache.
window.addEventListener('pageshow', () => openDB());

進行測試,確認您的網頁可供快取

Chrome 開發人員工具可協助您測試網頁,確保網頁經過最佳化調整,且能找出導致網頁不符資格的問題。

如何測試網頁:

  1. 在 Chrome 中前往該網頁。
  2. 在開發人員工具中,前往「應用程式」 >「往返快取」
  3. 按一下「Run Test」按鈕。接著,開發人員工具會嘗試離開並返回,判斷是否能透過 bfcache 還原頁面。
開發人員工具中的往返快取面板
開發人員工具中的「向後快取」面板。

如果測試成功,面板會回報「已從往返快取還原」。 如果失敗,面板就會顯示原因。如需完整的原因清單,請參閱「Chromium 未還原原因清單」。

如果原因可以幫助開發人員處理,面板會標示為「可操作」

開發人員工具回報無法從 Bfcache 還原網頁
bfcache 測試失敗,並提供可採取行動的結果。

在這張圖片中,使用 unload 事件監聽器會導致網頁不符合資格,無法使用 bfcache。如要修正此問題,請從 unload 切換為使用 pagehide

正確做法
window.addEventListener('pagehide', ...);
錯誤做法
window.addEventListener('unload', ...);

Lighthouse 10.0 還新增了 bfcache 稽核,可用於執行類似的測試。詳情請參閱 bfcache 稽核的說明文件

bfcache 對數據分析和效能評估的影響

如果您使用分析工具追蹤網站的造訪次數,您可能會發現在 Chrome 為更多使用者啟用 bfcache 後,回報的網頁總瀏覽量有所減少。

事實上,導入 bfcache 的其他瀏覽器所產生的網頁瀏覽量,可能都已遭到低估,因為最熱門的數據分析程式庫不會將 bfcache 還原成新的網頁瀏覽量來追蹤。

如要在網頁瀏覽計數中納入 bfcache 還原,請設定 pageshow 事件的事件監聽器,並檢查 persisted 屬性。

下例說明如何透過 Google Analytics (分析) 完成這項作業。其他分析工具可能使用類似的邏輯:

// Send a pageview when the page is first loaded.
gtag('event', 'page_view');

window.addEventListener('pageshow', (event) => {
  // Send another pageview if the page is restored from bfcache.
  if (event.persisted) {
    gtag('event', 'page_view');
  }
});

評估 bfcache 命中率

如要找出尚未使用 bfcache 的網頁,請按照下列步驟評估網頁載入的導覽類型:

// Send a navigation_type when the page is first loaded.
gtag('event', 'page_view', {
   'navigation_type': performance.getEntriesByType('navigation')[0].type;
});

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Send another pageview if the page is restored from bfcache.
    gtag('event', 'page_view', {
      'navigation_type': 'back_forward_cache';
    });
  }
});

使用 back_forward 瀏覽和 back_forward_cache 瀏覽的次數來計算 bfcache 命中率。

返回或向前瀏覽可能不會使用 bfcache 的原因,包括下列使用者行為:

  • 退出並重新啟動瀏覽器。
  • 複製分頁。
  • 關閉及還原分頁。

在某些情況下,瀏覽器可能會保留原始的導覽類型,並顯示 back_forward 類型,即使這些不是向後或向後瀏覽的情況。即使系統正確回報導覽類型,也會定期捨棄 bfcache 以節省記憶體。

因此,網站擁有者無法預期在所有 back_forward 導覽中,達到 100% 的 bfcache 命中率。不過,測量網頁比率有助於找出造成 bfcache 使用量的網頁。

Chrome 團隊目前正在開發 NotRestoredReasons API,以顯示網頁不使用 bfcache 的原因,以便開發人員改善 bfcache 命中率。

成效評估

bfcache 也會對欄位內收集的成效指標造成負面影響,特別是評估頁面載入時間的指標。

由於 bfcache 導覽功能會還原現有網頁,而不會啟動新網頁載入,因此啟用 bfcache 後,收集到的網頁載入總數會降低。不過,在資料集中,載入 bfcache 的網頁很有可能會取代網頁載入速度最快的問題,因為重複載入網頁 (包括往返瀏覽) 的速度通常比初次載入網頁還要快,因為採用了 HTTP 快取。因此,雖然改善使用者的網站效能,但啟用 bfcache 可能會使數據分析顯示的網頁載入速度變慢。

有幾種方法可以解決這個問題,一種是為所有載入網頁載入指標加上註解,並使用各自的導覽類型navigatereloadback_forwardprerender。這樣您就能持續監控這些導覽類型的效能,即使整體分佈情形出現負數也是如此。針對非以使用者為中心的網頁載入指標,例如第一個位元組 (TTFB),我們建議採用這種做法。

如果是網站體驗核心指標等以使用者為中心的指標,則更好的做法是回報更準確反映使用者體驗的值。

對網站體驗核心指標的影響

網站體驗核心指標可以評估使用者的網頁體驗,透過各種維度 (載入速度、互動性、視覺穩定性) 評估。網站體驗核心指標可以反映使用者體驗 bfcache 還原動作的速度,這比預設網頁載入更快。

這類工具會收集網站體驗核心指標報告 (例如 Chrome 使用者體驗報告) 並產生相關報表,會將 bfcache 還原作業視為資料集中的個別網頁造訪次數。在 bfcache 還原後就沒有專屬的網路效能 API 來測量這些指標,但您可以使用現有的網路 API 來估算這些指標的值:

  • 針對最大內容繪製 (LCP),請使用 pageshow 事件的時間戳記與下一個繪製影格的時間戳記之間的差異,因為影格中的所有元素會同時繪製。如果是 bfcache 還原,LCP 和 FCP 相同。
  • 針對「Interaction to Next Paint (INP)」,繼續使用現有的 Performance Observer,但將目前的 CLS 值重設為 0。
  • 在「Cumulative Layout Shift (CLS)」部分,繼續使用現有的 Performance Observer,但將目前的 CLS 值重設為 0。

如要進一步瞭解 bfcache 如何影響每項指標,請參閱個別 Core Web Vitals 的指標指南頁面。如需如何實作這些指標的 bfcache 版本的特定範例,請參閱將這些指標新增至 Web Vitals JS 程式庫的 PR

其他資源