Кэш для перехода назад и вперед
Оптимизируйте страницы, чтобы мгновенно загружать их при нажатии кнопок «Назад» и «Вперед» в браузере.
Кэш для перехода назад и вперед (или bfcache) — это функция оптимизации в браузере, которая позволяет мгновенно переходить вперед и назад по страницам. Это значительно повышает удобство просмотра веб-страниц, особенно для пользователей с медленно работающими устройствами или низкоскоростными подключениями к сети.
Веб-разработчики должны понимать, как оптимизировать страницы для bfcache, чтобы пользователям были доступны преимущества, обеспечиваемые этой функцией.
Совместимость с браузерами #
Функция bfcache уже много лет поддерживается в Firefox и в Safari на компьютерах и мобильных устройствах.
Начиная с версии 86, в Chrome для Android появилась поддержка bfcache для перехода между сайтами (для небольшого процента пользователей). В последующих выпусках разработчики постепенно дополняли перечень поддерживаемых версий и устройств. Начиная с версии 96, поддержка bfcache стала доступна для всех пользователей Chrome на компьютерах и мобильных устройствах.
Основные сведения о bfcache #
bfcache — это кэш в памяти, в котором сохраняется полный снимок страницы (включая кучу JavaScript), когда пользователь переходит с этой страницы на другую. Так как в памяти хранится вся страница, браузер может быстро и просто восстановить ее, если пользователь решит вернуться.
Сколько раз, посещая веб-сайт, вы переходили по ссылке на другую страницу, понимали, что это не то, что вам нужно, и нажимали кнопку «Назад»? В такие моменты bfcache может существенно уменьшить скорость загрузки предыдущей страницы.
С выключенным bfcache | Браузер инициирует новый запрос для загрузки предыдущей страницы, и (в зависимости от того, насколько хорошо эта страница оптимизирована для повторных посещений) ему, возможно, придется повторно загрузить, проанализировать и выполнить некоторые (или все) ресурсы, которые он недавно загружал. |
С включенным bfcache | Загрузка предыдущей страницы происходит практически мгновенно, потому что всю страницу можно восстановить из памяти без необходимости выходить в сеть. |
Посмотрев видеоролик о работе bfcache, вы поймете, насколько эта функция ускоряет переход по страницам:
В приведенном выше видеоролике браузер с включенным bfcache работает немного быстрее, чем браузер с выключенным bfcache.
bfcache не только ускоряет переход по страницам, но и уменьшает объем используемых данных, так как не нужно повторно загружать ресурсы.
Данные об использовании Chrome показывают, что каждый десятый переход на настольном компьютере и каждый пятый на мобильном устройстве выполняются либо на предыдущую, либо на следующую страницу. С включенным bfcache браузеры могут исключить этап передачи данных и не тратить время на загрузку миллиардов веб-страниц каждый день.
Как работает «кэш» #
«Кэш», используемый в функции bfcache, отличается от кэша HTTP (который также позволяет ускорить повторные переходы). bfcache сохраняет снимок всей страницы (включая кучу JavaScript) в памяти, тогда как в кэше HTTP содержатся только ответы на ранее сделанные запросы. Случаи, когда все запросы, необходимые для загрузки страницы, можно получить из кэша HTTP, довольно редки, поэтому повторные посещения страниц с использованием функции восстановления страниц из bfcache выполняются всегда быстрее, чем при переходах даже по самым оптимизированным веб-страницам без использования bfcache.
Создание снимка страницы в памяти сопровождается некоторыми сложностями, связанными с выбором наиболее подходящего метода сохранения выполняемого кода. Например, как обрабатывать вызовы функции setTimeout()
по истечении времени ожидания, когда страница находится в bfcache?
Ответ заключается в том, что браузеры приостанавливают выполнение всех ожидающих таймеров или несопоставленных обещаний (по сути, всех ожидающих задач в очередях задач JavaScript) и возобновляют обработку задач, когда (или если) они восстанавливают страницу из bfcache.
В некоторых случаях эта операция сопровождается довольно низким риском (например, для времени ожидания или обещаний), но в других случаях это может привести к очень запутанному или непредвиденному поведению. Например, если браузер приостанавливает задачу, которая требуется для выполнения транзакции IndexedDB, это может повлиять на другие открытые вкладки в том же источнике (так как к одним и тем же базам данных IndexedDB могут получать доступ несколько вкладок одновременно). В результате браузеры, как правило, не пытаются кэшировать страницы при выполнении транзакций IndexedDB или при использовании API, которые могут повлиять на другие страницы.
Дополнительные сведения о том, как использование различных API влияет на соответствие страниц требованиям bfcache, см. в разделе «Оптимизация страниц для bfcache» ниже.
API для наблюдения за bfcache #
Несмотря на то что браузеры выполняют операции с bfcache автоматически, разработчикам важно знать, когда происходят такие операции, чтобы оптимизировать страницы для bfcache и соответствующим образом скорректировать все метрики или показатели производительности.
Основные события, используемые для наблюдения за bfcache, — события перехода со страницы на страницу (pageshow
и pagehide
). Они существуют с тех пор, как появилась функция bfcache, и поддерживаются почти во всех используемых в настоящее время браузерах.
Кроме того, при операциях записи или удаления страницы в bfcache, а также в некоторых других ситуациях возникают новые события Page Lifecycle — freeze
и resume
. Например, при «заморозке» фоновой вкладки с целью минимизации использования ЦП. Обратите внимание, что в настоящее время события Page Lifecycle поддерживаются только в браузерах на основе Chromium.
Наблюдение за восстановлением страниц из bfcache #
Событие pageshow
возникает сразу после события load
при первоначальной загрузке страницы, а также при каждом восстановлении страницы из bfcache. У события pageshow
есть свойство persisted
, которое имеет значение true
, если страница была восстановлена из bfcache (и значение false
в противном случае). С помощью свойства persisted
можно отличать обычную загрузку страниц от их восстановления из bfcache. Пример:
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('Эта страница была восстановлена из bfcache.');
} else {
console.log('Эта страница была загружена обычным способом.');
}
});
В браузерах, поддерживающих API Page Lifecycle, событие resume
возникает при восстановлении страниц из bfcache (непосредственно перед событием pageshow
). Кроме того, оно возникает, когда пользователь повторно посещает «замороженную» фоновую вкладку. Если нужно восстановить состояние страницы после ее «заморозки» (включая страницы в bfcache), можно использовать событие resume
, но если вы хотите измерить процент попаданий своего сайта в bfcache, потребуется использовать событие pageshow
. В некоторых случаях вам может потребоваться использовать оба события.
Наблюдение за добавлением страниц в bfcache #
Событие pagehide
— аналог события pageshow
. Событие pageshow
возникает при обычной загрузке страницы либо ее восстановлении из bfcache. Событие pagehide
возникает при обычной выгрузке страницы либо когда браузер пытается поместить ее в bfcache.
У события pagehide
также есть свойство persisted
; если оно имеет значение false
, можно с уверенностью сказать, что страница не будет помещена в bfcache. Но если свойство persisted
имеет значение true
, это вовсе не гарантирует, что страница будет помещена в кэш. Это означает, что браузер намеревается поместить ее в кэш, но могут возникнуть факторы, из-за которых не удастся выполнить кэширование.
window.addEventListener('pagehide', (event) => {
if (event.persisted === true) {
console.log('Эта страница *может* попасть в bfcache.');
} else {
console.log('Эта страница будет выгружена в обычном режиме, а затем будет утеряна.');
}
});
Аналогично, событие freeze
возникает сразу после события pagehide
(если свойство persisted
имеет значение true
), но, опять же, это означает, что браузер всего лишь намеревается кэшировать страницу. Возможно, ему все же придется отказаться от этого намерения по ряду описанных ниже причин.
Оптимизация страниц для bfcache #
Не все страницы сохраняются в bfcache, и даже если страница действительно сохранена в bfcache, она не будет оставаться там бесконечно. Разработчикам очень важно понимать, благодаря чему страницы могут быть пригодными (или непригодными) для помещения в bfcache, чтобы добиться максимального показателя попаданий страниц в кэш.
В следующих разделах приведены рекомендации, следуя которым, можно добиться максимальной вероятности кэширования страниц в браузере.
Никогда не используйте событие unload
#
Самый важный способ оптимизировать страницы для bfcache во всех браузерах — никогда не использовать событие unload
. Без каких-либо исключений!
Событие unload
создает проблемы для браузеров, так как оно возникает до операций с bfcache, и при разработке многих страниц в Интернете предполагается (обоснованно), что после возникновения события unload
страница перестает существовать. В связи с этим возникает проблема, так как многие из страниц также созданы с расчетом на то, что событие unload
будет срабатывать каждый раз, когда пользователь покидает страницу, а это уже давно не соответствует действительности.
Таким образом, для браузеров возникает дилемма: нужно повысить удобство для пользователя, но при этом существует риск нарушить нормальную работу страницы.
В Chrome и Firefox страницы считаются неподходящими для помещения в bfcache, если они добавляют прослушиватель события unload
. Это не очень рискованный подход, но при его использовании происходит «дисквалификация» большого количества страниц. Safari пытается кэшировать некоторые страницы с прослушивателем события unload
, но для уменьшения риска нарушить работу страницы этот браузер не создает событие unload
, когда пользователь покидает страницу, и в результате это событие становится очень ненадежным.
Вместо события unload
используйте событие pagehide
. В настоящее время событие pagehide
возникает во всех случаях, когда возникает событие unload
. Кроме того, оно возникает при помещении страницы в bfcache.
В Lighthouse 6.2.0 был добавлен аудит no-unload-listeners audit
, который предупреждает разработчиков, если какой-либо код JavaScript на страницах (включая код из сторонних библиотек) добавляет прослушиватель события unload
.
Добавляйте прослушиватели события beforeunload
только при определенных условиях #
Событие beforeunload
не препятствует размещению страниц в bfcache в Chrome или Safari, но делает это невозможным в Firefox, поэтому не используйте его без крайней необходимости.
Тем не менее (в отличие от события Неправильно Правильноunload
) в некоторых случаях допустимо использовать событие beforeunload
. Например, если нужно предупредить пользователя о том, что есть несохраненные изменения, который будут утеряны, если пользователь покинет страницу. В этом случае рекомендуется добавлять прослушиватели события beforeunload
только тогда, когда у пользователя есть несохраненные изменения, а затем удалять их сразу после сохранения изменений.window.addEventListener('beforeunload', (event) => {
if (pageHasUnsavedChanges()) {
event.preventDefault();
return event.returnValue = 'Вы действительно хотите выйти?';
}
});function beforeUnloadListener(event) {
event.preventDefault();
return event.returnValue = 'Вы действительно хотите выйти?';
};
// Функция, которая создает обратный вызов, если на странице есть несохраненные изменения.
onPageHasUnsavedChanges(() => {
window.addEventListener('beforeunload', beforeUnloadListener);
});
// Функция, которая создает обратный вызов, если вопрос с несохраненными изменениями на странице решен.
onAllChangesSaved(() => {
window.removeEventListener('beforeunload', beforeUnloadListener);
});
Не используйте ссылки на window.opener #
В некоторых браузерах (включая браузеры на основе Chromium) происходит следующее: если страница открыта с использованием window.open()
или (в браузерах на основе Chromium до версии 88) по ссылке с target=_blank
без указания rel="noopener"
, то открывающаяся страница будет иметь ссылку на объект window открытой страницы.
Помимо угрозы безопасности страницу с непустой ссылкой window.opener
невозможно безопасно поместить в bfcache, так как это может нарушить работу всех страниц, пытающихся получить доступ к этой странице.
В результате, если возможно, рекомендуется не создавать ссылки на window.opener
с использованием атрибута rel="noopener"
. Если сайту требуется открывать окна и управлять ими с помощью window.postMessage()
либо напрямую ссылаться на объект window, ни открытое окно, ни opener не будут пригодны для размещения в bfcache.
Всегда закрывайте открытые подключения, прежде чем пользователь покинет страницу #
Как было сказано выше, когда браузер помещает страницу в bfcache, он приостанавливает все запланированные задачи JavaScript, а при извлечении страницы из кэша возобновляет работу этих задач.
Если запланированные задачи JavaScript обращаются только к DOM API или другим API, изолированным и используемым только для текущей страницы, то приостановка выполнения этих задач, когда страница не видна пользователю, не приводит ни к каким проблемам.
Но если эти задачи связаны с API, которые также доступны с других страниц в том же источнике (например, IndexedDB, Web Locks, WebSockets и т. д.), могут возникать проблемы, так как приостановка таких задач может помешать выполнению кода на других вкладках.
В результате некоторые браузеры не пытаются помещать страницы в bfcache в указанных ниже сценариях.
- Страницы с открытым подключением к IndexedDB
- Страницы с выполняющейся функцией fetch () или запросом XMLHttpRequest
- Страницы с открытым подключением к WebSocket или WebRTC
Если в странице используется какой-либо из этих API, рекомендуется всегда закрывать подключения и удалять или отключать наблюдателей во время событий pagehide
или freeze
. Это позволит браузеру безопасно кэшировать страницу, не затрагивая при этом другие открытые вкладки.
После восстановления страницы из bfcache можно повторно открыть эти API или повторно подключиться к ним (в событии pageshow
или resume
).
В примере ниже показано, как сделать страницы пригодными для размещения в bfcache при использовании IndexedDB, закрыв открытое подключение в прослушивателе события pagehide
:
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;
}
// Закрываем подключение к базе данных, когда пользователь покидает страницу.
window.addEventListener('pagehide', () => {
if (dbPromise) {
dbPromise.then(db => db.close());
dbPromise = null;
}
});
// Открываем подключение при загрузке страницы или ее восстановлении из bfcache.
window.addEventListener('pageshow', () => openDB());
Обновляйте устаревшие или конфиденциальные данные после восстановления страницы из bfcache #
Если ваш сайт сохраняет сведения о состоянии пользователя (особенно это касается любой конфиденциальной информации пользователя), то после восстановления страницы из bfcache необходимо обновить или удалить эти данные.
Например, если пользователь переходит на страницу оформления заказа, а затем обновляет свою корзину, то при переходе назад и восстановлении устаревшей страницы из bfcache может отображаться устаревшая информация.
Другой, более важный пример, — когда пользователь выходит из системы на сайте на общедоступном компьютере, а следующий пользователь нажимает кнопку «Назад». В этом случае могут быть раскрыты личные данные, которые, как предполагал первый пользователь, были удалены при выходе из системы.
Чтобы не допустить подобных ситуаций, рекомендуется всегда обновлять страницу после события pageshow
, если атрибут event.persisted
имеет значение true
.
Следующий код проверяет, есть ли файл cookie для конкретного сайта в событии pageshow
, и если такого файла нет, выполняет повторную загрузку:
window.addEventListener('pageshow', (event) => {
if (event.persisted && !document.cookie.match(/my-cookie/)) {
// Принудительное выполнение повторной загрузки, если пользователь вышел из системы.
location.reload();
}
});
Выполните тестирование и убедитесь, что страницы пригодны для кэширования #
С помощью Chrome DevTools можно тестировать страницы, проверять, оптимизированы ли они для bfcache, и выявлять все проблемы, из-за которых страницы могут быть непригодны для кэширования.
Чтобы протестировать определенную страницу, перейдите на нее в Chrome, а затем в DevTools перейдите в раздел Application > Back-forward Cache (Приложение > Кэш для перехода назад и вперед). Затем нажмите кнопку Run Test (Выполнить тест). DevTools попытается покинуть страницу, а затем вернуться на ее, чтобы определить, можно ли восстановить страницу из bfcache.

При успешном выполнении операции на панели отобразится сообщение Restored from back-forward cache (Восстановлено из кэша для перехода назад и вперед):

В случае неудачи на панели отобразится сообщение о том, что страница не была восстановлена, и будет указана причина этого. Если вы, как разработчик, можете устранить причину проблемы, это также будет указано:

На приведенном выше снимке экрана показано, что из-за применения прослушивателя события Неправильно Правильноunload
страница не подходит для размещения в bfcache. Чтобы решить эту проблему, можно вместо события unload
использовать событие pagehide
:window.addEventListener('unload', ...);
window.addEventListener('pagehide', ...);
Как bfcache влияет на аналитику и на измерение производительности #
Если вы отслеживаете посещения своего сайта с помощью средства аналитики, вы, вероятно, заметите уменьшение общего количества просмотров страниц, так как Chrome будет использовать bfcache для все большего числа пользователей.
На самом деле у вас, вероятно, уже занижены показатели количества просмотров страниц из других браузеров, в которых реализована функция bfcache, так как большинство популярных библиотек аналитики не считают операции восстановления из bfcache новыми просмотрами страниц.
Если вы не хотите, чтобы количество просмотров страниц уменьшалось из-за включения bfcache в Chrome, можно сделать так, чтобы операции восстановления из bfcache считались просмотрами страниц (рекомендуется). Для этого нужно прослушивать событие pageshow
и проверять свойство persisted
.
В следующем примере показано, как сделать это с помощью Google Analytics; в других средствах аналитики логика должна быть аналогичной:
// Отправка события pageview при первой загрузке страницы.
gtag('event', 'page_view');
window.addEventListener('pageshow', (event) => {
if (event.persisted === true) {
// Отправка еще одного события pageview при восстановлении страницы из bfcache.
gtag('event', 'page_view');
}
});
Измерение производительности #
Использование bfcache также может отрицательно сказаться на показателях производительности, собираемых «на периферии», в частности на показателях, измеряющих время загрузки страницы.
При переходах со страницы на страницу с использованием bfcache браузер восстанавливает существующую страницу, а не инициирует загрузку новой страницы, поэтому при включении bfcache подсчитываемое общее количество загрузок страниц будет уменьшаться. Тем не менее есть один критически важный момент: если вместо загрузки страниц восстанавливать их из bfcache, это, вероятно, будут самые быстрые операции загрузки в вашем наборе данных. Это связано с тем, что по определению переходы назад и вперед представляют собой повторные посещения, а повторная загрузка страниц обычно выполняются быстрее, чем загрузка страниц для посетителей, впервые посещающих ваш сайт (как упоминалось ранее, из-за кэширования HTTP).
В результате в вашем наборе данных будет меньше быстрых загрузок страниц. Это, вероятно, уменьшит показатель распространения несмотря на то что производительность, наблюдаемая пользователем, вероятно, возрастет.
Эту проблему можно решить несколькими способами, один из которых — аннотировать все метрики загрузки страницы с указанием соответствующего типа перехода: navigate
, reload
, back_forward
или prerender
. Благодаря этому вы сможете по-прежнему следить за производительностью страниц при переходах таких типов, даже если общее распределение изменится в отрицательную сторону. Этот подход рекомендуется для метрик загрузки страниц, не ориентированных на пользователей, например для метрики Time to First Byte (TTFB) (Время до первого байта).
Для метрик, ориентированных на пользователей, например Core Web Vitals, лучший вариант — сообщать значение, которое более точно отражает удобство для пользователя.
Влияние на метрики Core Web Vitals #
Метрики Core Web Vitals позволяют измерять, насколько пользователю удобно работать с веб-страницей, по различным параметрам (скорость загрузки, интерактивность, визуальная стабильность). И так как для пользователя восстановление из bfcache выглядит как более быстрый переход по сравнению с традиционной загрузкой страницы, важно, чтобы это было отражено в метриках Core Web Vitals. В конце концов, пользователю все равно, включен ли bfcache; ему просто нужно, чтобы переходы со страницы на страницу выполнялись быстро.
В наборах данных ряда средств (например, отчета Chrome User Experience Report), которые собирают метрики Core Web Vitals и формируют отчеты по ним, операции восстановления из bfcache считаются отдельными посещениями страниц.
И хотя (пока еще) нет специализированных API для оценки производительности веб-страниц, позволяющих измерять эти метрики после операций восстановления из bfcache, значения таких метрик можно аппроксимировать с помощью существующих веб-API.
- Для метрики Largest Contentful Paint (LCP) (Отображение самого крупного элемента содержимого) можно использовать разницу между меткой времени события
pageshow
и меткой времени следующего отображенного фрейма (так как все элементы во фрейме отображаются одновременно). Обратите внимание, что при выполнении операции восстановления из bfcache значения метрик LCP и FCP будут одинаковыми. - Для метрики First Input Delay (FID) (Задержка до первого ввода данных) можно повторно добавить прослушиватели событий (те же, что используются полифиллом FID) в событие
pageshow
и передавать значение FID в качестве показателя задержки до первого ввода данных после операции восстановления из bfcache. - Для метрики Cumulative Layout Shift (CLS) (Совокупное смещение макета) можно продолжать использовать существующий API Performance Observer; все, что вам потребуется сделать, — сбросить текущее значение метрики CLS до нуля.
Дополнительные сведения о том, как bfcache влияет на каждую метрику, см. на страницах руководств по метрикам Core Web Vitals. Пример реализации версий этих метрик для bfcache в коде см. в запросе на вытягивание для добавления этих метрик в библиотеку JS web-vitals.
Дополнительные ресурсы #
- Использование кэширования в Firefox (bfcache в Firefox)
- Page Cache (bfcache в Safari)
- Кэш для переходов назад и вперед: действия в Интернете {nbsp}(различия в bfcache в разных браузерах)
- Средство тестирования bfcache (проверка того, как различные API и события влияют на bfcache в браузерах)