使用瀏覽器的上一頁和下一頁按鈕時,可以進行網頁最佳化,讓網頁立即載入。
往返快取 (或 Bfcache) 是瀏覽器最佳化功能,可提供即時往返瀏覽。進而大幅改善使用者的瀏覽體驗,特別是網路或裝置速度較慢的使用者。
身為網頁程式開發人員,請務必瞭解如何針對所有瀏覽器進行 Bfcache 調整網頁最佳化,讓使用者能夠從中獲益。
瀏覽器相容性
電腦和行動裝置的 Firefox 和 Safari 已支援 bfcache。
自第 86 版起,Chrome 已為少數使用者啟用 Android 上的跨網站導覽功能。在後續版本中,系統會逐步推出額外支援。自 96 版起,電腦和行動裝置的所有 Chrome 使用者都會啟用 bfcache。
bfcache 基本概念
bfcache 是記憶體內的快取,會在使用者離開時儲存網頁的完整快照 (包括 JavaScript 堆積)。透過記憶體中的整個頁面,當使用者決定返回時,瀏覽器可以輕鬆快速地還原頁面。
得知有多少次造訪網站並點按連結前往其他頁面,卻只是意識到這個網頁並不符合你的預期,然後點選返回按鈕呢?這時,bfcache 就能在先前網頁的載入速度方面產生巨大變化:
未啟用 bfcache | 系統會啟動新的要求載入上一頁,並根據該網頁對重複造訪進行 最佳化的成效而定,瀏覽器可能需要重新下載、重新剖析及重新執行剛才下載的部分資源。 |
已啟用 bfcache | 載入前一個網頁的動作基本上是立即可用,因為整個網頁都能從記憶體還原,無需前往網路 |
請觀看這部影片,瞭解 bfcache 實際運作的速度,瞭解載入網頁的速度。
在上面的影片中,使用 bfcache 的範例比不使用 bfcache 的少一些。
bfcache 不僅能加快瀏覽速度,還能降低資料用量,因為不需要重新下載資源。
Chrome 使用資料顯示,在電腦上瀏覽 10 次時,每 10 次瀏覽中就有 1 次是往返網頁。啟用 bfcache 後,瀏覽器就能免除每天數十億個網頁的資料移轉和載入時間!
「快取」的運作方式
bfcache 使用的「快取」與 HTTP 快取不同 (這在加快重複瀏覽速度時相當實用)。bfcache 是記憶體中整個網頁的快照 (包括 JavaScript 堆積),而 HTTP 快取僅包含先前所發出要求的回應。載入網頁所需的所有要求極少都可以透過 HTTP 快取執行,因此使用 bfcache 還原功能重複造訪的速度會比只取得最佳非 bfcache 瀏覽的速度更快。
不過,在記憶體中建立頁面的快照,會牽涉到如何保留處理中程式碼的最佳方式。舉例來說,當網頁位於 Bfcache 時,要如何處理超過逾時的 setTimeout()
呼叫?
解決方法是,瀏覽器會暫停執行所有待處理的計時器或未解決的承諾 (通常都是 JavaScript 工作佇列中的所有待處理工作),並且在 bfcache 還原網頁後繼續處理工作。
在某些情況下,這會相當低風險 (例如逾時或承諾),但在其他情況下則可能會產生非常混淆或非預期的行為。舉例來說,如果瀏覽器暫停索引資料庫交易中的必要工作,可能會影響相同來源中其他開啟的分頁 (因為相同的索引資料庫資料庫可同時透過多個分頁存取)。因此,瀏覽器通常不會嘗試在 IndexedDB 交易中快取網頁,也不會使用可能影響其他頁面的 API。
如要進一步瞭解各種 API 使用情形如何影響網頁的 bfcache 使用資格,請參閱下方的針對 bfcache 最佳化網頁。
bfcache 與單頁應用程式 (SPA)
bfcache 支援瀏覽器管理的導覽。因此,在 SPA 中不適用「軟導覽」,但 SPA 的主要賣點之一就是那些瀏覽方式應該很快。不過,bfcache 在「返回」SPA 時一定會有幫助,不必從頭開始重新初始化該應用程式。
用來觀察 bfcache 的 API
雖然 bfcache 是瀏覽器會自動執行的最佳化作業,但開發人員仍必須瞭解網頁發生的時間,以便據此為網頁進行最佳化調整,並據此調整任何指標或效能評估方式。
用於觀察 bfcache 的主要事件是頁面轉換作業 pageshow
和 pagehide
,這個事件的存在時間為 bfcache 的多久後,且在目前使用的所有瀏覽器中都獲得支援。
當頁面進入或移出 Bfcache 頁面後,在其他情況下,也會分派較新的頁面生命週期事件 (freeze
和 resume
)。例如背景分頁凍結,以盡量減少 CPU 使用率。請注意,頁面生命週期事件目前僅支援以 Chromium 為基礎的瀏覽器。
觀察網頁何時透過 Bfcache 還原
頁面初次載入,以及任何網頁透過 bfcache 還原時,都會在 load
事件之後觸發 pageshow
事件。pageshow
事件具有 persisted
屬性,如果網頁是從 bfcache 還原,將會是 true
(反之則為 false
)。您可以使用 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 還原時觸發 resume
事件 (緊接在 pageshow
事件之前),不過也會在使用者再次造訪凍結的背景分頁時觸發。如果您想在頁面凍結後更新網頁狀態 (包括 bfcache 中的網頁),可以使用 resume
事件,但如果想評估網站的 bfcache 命中率,必須使用 pageshow
事件。在某些情況下,您可能需要同時使用兩者。
觀察網頁何時進入 bfcache
pagehide
事件是 pageshow
事件的對應項目。如果網頁正常載入,或透過 bfcache 還原,就會觸發 pageshow
事件。當網頁正常卸載,或瀏覽器嘗試將網頁放入 bfcache 時,就會觸發 pagehide
事件。
pagehide
事件也具有 persisted
屬性,如果是 false
,就能確保網頁不會即將進入 bfcache。不過,如果 persisted
屬性是 true
,則不保證一定能快取網頁。
這表示瀏覽器「預期」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.');
}
});
同樣地,freeze
事件也會在 pagehide
事件之後 (如果事件的 persisted
屬性為 true
) 立即觸發,但這只是表示瀏覽器「想要」intends快取網頁。但基於下列幾種原因,可能還是必須捨棄此內容。
針對網頁快取進行網頁最佳化
並非所有網頁都會儲存在 bfcache 中,即使網頁已經儲存在快取中,也不會無限期保留。開發人員必須瞭解怎樣才符合 (或不符合資格) 對 bfcache 的快取命中率。
以下各節將概略說明最佳做法,盡可能使瀏覽器能快取您的網頁。
永不使用「unload
」事件
如要在所有瀏覽器中針對 bfcache 進行最佳化,最重要的方法是一律不使用 unload
事件。當然!
unload
事件會對瀏覽器造成問題,因為會預先 bfcache 和網際網路上的許多網頁在執行 (合理的) 假設在 unload
事件觸發後繼續存在。這存在困難之處,因為其中許多頁面都「也」假設在使用者離開時,unload
事件會觸發,而此事件不再真實,且長期以來並未真實存在。
因此瀏覽器目前面臨兩難的困境,除了要改善使用者體驗,也必須做出選擇,但也可能破壞網頁。
在電腦上,Chrome 和 Firefox 會選擇加入 unload
事件監聽器,使其不符合網頁的快取使用,雖然風險較低,但同時符合「許多」網頁的規定。Safari 會嘗試使用 unload
事件監聽器快取部分網頁,但為了減少可能發生中斷情形,不會在使用者進行導航時執行 unload
事件,導致事件變得非常不可靠。
在行動裝置上,Chrome 和 Safari 會嘗試使用 unload
事件監聽器快取網頁,因為發生中斷風險的風險更低,因為 unload
事件在行動裝置上一直以來非常不可靠。Firefox 會將使用 unload
的網頁視為不適用 bfcache;在 iOS 上則需要所有瀏覽器都必須使用 WebKit 轉譯引擎,因此運作方式類似 Safari。
請改用 pagehide
事件,不要使用 unload
事件。pagehide
事件會在 unload
事件目前觸發的所有情況下觸發,而且也會在網頁放入 bfcache 時觸發。
事實上,Lighthouse 有一項 no-unload-listeners
稽核,可在網頁中的任何 JavaScript (包括第三方程式庫) 新增 unload
事件監聽器時警告開發人員。
由於擴充功能的穩定性及對 bfcache 的效能影響,Chrome 即將淘汰 unload
事件。
使用權限政策避免在網頁上使用卸載處理常式
如果網站未使用 unload
事件處理常式,可以使用 Chrome 115 的權限政策,確保不會新增這些事件。
Permission-Policy: unload()
這麼做可以新增卸載處理常式,讓網站無法使用快取功能,避免第三方或擴充功能拖慢網站運作速度。
只在有條件的情況下新增 beforeunload
事件監聽器
beforeunload
事件不會讓你的網頁無法在新版瀏覽器快取中使用 bfcache,但之前這樣,但這個事件仍不可靠,因此除非絕對必要,否則請避免使用此功能。
不過,與 unload
事件不同,beforeunload
有正當用途。舉例來說,如果您希望通知使用者他們沒有儲存的變更,只要離開頁面,就會遺失。在此情況下,建議您只在使用者有未儲存的變更時新增 beforeunload
事件監聽器,然後在儲存變更後立即移除。
window.addEventListener('beforeunload', (event) => { if (pageHasUnsavedChanges()) { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?'; } });
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
的所有網頁都不符合往返快取資格。
由於 Cache-Control: no-store
會限制網頁是否適用 bfcache 的規定,因此請只在內含機密資訊的網頁上設定這項設定,因為任何類型都不適合快取。
如果網頁要一律提供最新內容,而且其中不含機密資訊,請使用 Cache-Control: no-cache
或 Cache-Control: max-age=0
。這些指令會指示瀏覽器在放送內容前重新驗證內容,也不會影響網頁是否可使用快取。
請注意,透過 bfcache 還原網頁時,系統會從記憶體還原網頁,而不是從 HTTP 快取還原。因此,系統不會將 Cache-Control: no-cache
或 Cache-Control: max-age=0
這類指令納入考量,也不會在顯示內容前重新驗證。
這仍然可能提供更好的使用者體驗,但 bfcache 還原作業會立即執行,且因為網頁不長時間留在 bfcache 中,所以不太可能會過期內容。不過,如果您的內容每分鐘都變動,您可以使用 pageshow
事件擷取任何更新,如下一節所述。
在 bfcache 還原後更新過時或機密資料
如果您的網站會保留使用者狀態 (尤其是任何敏感的使用者資訊),從 bfcache 還原網頁後,資料就需要更新或清除。
舉例來說,如果使用者前往結帳頁面並更新購物車,如果在 bfcache 還原過時的網頁,返回導覽可能會公開過時的資訊。
另一個更重要的例子,就是使用者在公用電腦上登出網站,而下一位使用者點選返回按鈕。這可能會導致使用者假設在登出時已清除的私人資料。
為避免發生這類情況,如果 event.persisted
為 true
,建議一律在 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 在往返瀏覽各網頁的前/向後顯示一組新的廣告。然而,除了對成效的影響外,也不必擔心這種行為能否提升廣告參與度。使用者可能已經注意到,他們打算回訪並按下某個廣告,但是重新載入,而不是從無法用的快取中還原。在做出假設之前,請務必測試這個情境 (最好使用 A/B 測試)。
如果網站需要重新整理 bfcache 還原廣告,然後在 event.persisted
為 true
時僅重新整理 pageshow
事件上的廣告,這樣做不會影響網頁效能。請與您的廣告供應商聯絡,但參考這裡的範例,瞭解如何使用 Google 發布商廣告代碼執行此動作。
避免使用 window.opener
個參照
在舊版瀏覽器中,如果網頁是透過 target=_blank
的連結使用 window.open()
開啟,而無須指定 rel="noopener"
,開啟的頁面就會提供開啟網頁的視窗物件參照。
除了存在安全性風險之外,含非空值 window.opener
參照的頁面也不得安全地放入 bfcache,因為這麼做可能導致任何試圖存取該網頁的網頁毀損。
因此,最好避免建立 window.opener
參照。方法是盡可能使用 rel="noopener"
(請注意,現在所有新式瀏覽器中皆是預設值)。如果您的網站需要開啟視窗並透過 window.postMessage()
控制視窗,或直接參照視窗物件,則開啟的視窗和開啟器都無法使用 bfcache。
一律在使用者離開前關閉開放連線
如前文所述,將網頁放入 bfcache 後,所有已排定的 JavaScript 工作都會暫停,並在頁面從快取中移除時恢復執行。
如果這些排定的 JavaScript 工作只能存取 DOM API (或僅存取目前的頁面),請暫停這些工作,而不對使用者造成任何問題。
但是,如果這些工作已連結至可透過相同來源的其他頁面 (例如 IndexedDB、Web Lock、WebSocket 等) 存取的 API,這可能會產生問題,因為暫停這些工作可能會導致其他分頁中的程式碼無法運作。
因此,在下列情況下,部分瀏覽器不會嘗試將網頁放在 bfcache:
- 內含開放式 IndexedDB 連線的網頁
- 含有進行中 fetch() 或 XMLHttpRequest 的網頁
- 使用開放式 WebSocket 或 WebRTC 連線的網頁
如果您的網頁使用上述任何 API,最好在 pagehide
或 freeze
事件期間一律關閉連線,並移除或取消連結觀察器。如此可讓瀏覽器安全地快取網頁,避免影響其他開啟的分頁。
接著,如果網頁是透過 bfcache 還原,您可以重新開啟或重新連結這些 API (在 pageshow
或 resume
事件中)。
以下範例說明如何在 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 is leaving.
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 開發人員工具可協助您測試網頁,確保網頁經過最佳化調整,並識別出任何可能導致網頁不符資格的問題。
如要測試特定網頁,請在 Chrome 中前往該網頁,然後在開發人員工具中依序前往「Application」 >「Back-forward Cache」(後端快取)。接著點選「Run Test」按鈕,開發人員工具就會嘗試離開並返回,判斷是否能透過 bfcache 還原頁面。
如果成功,面板會顯示「已從往返快取還原」訊息:
如果失敗,面板會顯示網頁未還原的原因,並列出原因。
如果原因是開發人員可以解決的問題,系統也會提供以下資料:
在上方螢幕截圖中,使用 unload
事件監聽器會防止網頁使用 bfcache。如要解決這個問題,請從 unload
切換為使用 pagehide
:
window.addEventListener('unload', ...);
window.addEventListener('pagehide', ...);
Lighthouse 10.0 還新增了 bfcache 稽核功能,這項功能會執行與開發人員工具相同的測試,並提供稽核失敗的原因,說明網頁不符資格的原因。詳情請參閱 bfcache 稽核的說明文件。
bfcache 對數據分析和效能評估的影響
如果您使用分析工具追蹤網站的造訪次數,您可能會發現在 Chrome 持續為更多使用者啟用 bfcache 後,系統回報的網頁總瀏覽量有所減少。
事實上,您可能「已經」忽略其他採用 bfcache 的瀏覽器網頁瀏覽量,因為大多數的熱門數據分析程式庫都不會將 bfcache 還原動作視為新的網頁瀏覽來追蹤。
如果您不希望 Chrome 啟用 bfcache 而導致網頁瀏覽計數減少,可以監聽 pageshow
事件並檢查 persisted
屬性,將 bfcache 還原作業回報為網頁瀏覽 (建議做法)。
以下範例說明如何使用 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 是否使用 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 還原作業,可能是資料集內的一些載入速度最快的網頁。這是因為往前或向後瀏覽,即重複造訪,而重複載入網頁的速度,通常比首次訪客載入頁面的速度更快 (如前文所述,因為 HTTP 快取的緣故)。
以減少資料集內網頁載入的速度變少,並且可能會使分佈速度變慢,儘管使用者體驗的表現可能有所提升!
有幾種方法可以解決這個問題,一種是為所有載入網頁載入指標加上註解,並使用各自的導覽類型:navigate
、reload
、back_forward
或 prerender
。這樣您就能持續監控這些導覽類型的效能,即使整體分佈偏差亦然。這個方法適用於非以使用者為中心的頁面載入指標,例如 Time to First Byte (TTFB)。
如果是網站體驗核心指標等以使用者為中心的指標,則更好的做法是回報更準確反映使用者體驗的值。
對網站體驗核心指標的影響
Core Web Vitals 指標會從各種維度 (載入速度、互動性、視覺穩定性) 評估使用者的網頁體驗,且使用者體驗 bfcache 的還原程序比傳統網頁載入更快,因此網站體驗核心指標的指標必須能反映這一點。畢竟,使用者不在意是否啟用 bfcache 了,只要注意瀏覽速度很快就夠了!
Chrome 使用者體驗報告這類工具會收集網站體驗核心指標指標並製作報表,這類工具會將 bfcache 還原作業視為資料集中的個別網頁造訪次數。
雖然目前還沒有專屬的網路效能 API,用於在 bfcache 還原後評估這些指標,但您可以使用現有的網路 API 估算這些指標的值。
- 針對最大內容繪製 (LCP),您可以使用
pageshow
事件的時間戳記,到下一個已繪製影格的時間戳記 (因為影格中的所有元素會同時繪製)。請注意,如果是 bfcache 還原,LCP 和 FCP 會相同。 - 針對首次輸入延遲時間 (FID),您可以在
pageshow
事件中重新新增事件監聽器 (與 FID polyfill 所用的相同),並在 bfcache 還原後將首次輸入的延遲時間回報為 FID。 - 針對「Cumulative Layout Shift (CLS)」,您可以繼續使用現有的 Performance Observer,只需將目前的 CLS 值重設為 0 即可。
如要進一步瞭解 bfcache 對每項指標的影響,請參閱個別 Core Web Vitals 的指標指南頁面。如需具體範例,瞭解如何在程式碼中實作這些指標的 Bfcache 版本,請參閱將指標新增至 Web-vitals JS 程式庫的 PR。
其他資源
- Firefox 快取 (Firefox 中的 bfcache)
- 網頁快取 (Safari 中的 bfcache)
- 往返快取:網頁公開行為 (不同瀏覽器之間的快取差異)
- bfcache 測試人員(測試不同的 API 和事件如何影響瀏覽器中的 bfcache)
- 效能遊戲變更工具:瀏覽器往返快取 (Smashing Magazine 的個案研究,說明啟用 bfcache 改善網站體驗核心指標的顯著改善項目)