Опубликовано: 25 мая 2023 г., Последнее обновление: 2 июля 2026 г.
Кэширование "назад/вперед" (или bfcache) — это оптимизация браузера, обеспечивающая мгновенную навигацию назад и вперед. Она значительно улучшает работу в браузере, особенно для пользователей с медленным интернетом или устройствами.
Для веб-разработчиков крайне важно понимать, как оптимизировать страницы для bfcache , чтобы пользователи могли получить от этого максимальную выгоду.
Совместимость с браузерами
Все основные браузеры включают bfcache, в том числе Chrome (начиная с версии 96), Firefox и Safari .
основы bfcache
При использовании кэша "назад/вперед" (bfcache) вместо удаления страницы при переходе пользователя на другую страницу, мы откладываем удаление и приостанавливаем выполнение JavaScript. Если пользователь вскоре вернется на предыдущую страницу, мы снова сделаем ее видимой и возобновим выполнение JavaScript. Это приводит к практически мгновенной навигации по страницам для пользователя.
Сколько раз вы заходили на сайт, переходили по ссылке на другую страницу, а потом понимали, что это не то, что вам нужно, и нажимали кнопку «Назад»? В такой ситуации bfcache может существенно ускорить загрузку предыдущей страницы:
| Без включенного bfcache | Инициируется новый запрос для загрузки предыдущей страницы, и, в зависимости от того, насколько хорошо эта страница оптимизирована для повторных посещений, браузеру может потребоваться повторно загрузить, повторно проанализировать и повторно выполнить некоторые (или все) ресурсы, которые он только что загрузил. |
| При включенном bfcache | Загрузка предыдущей страницы происходит практически мгновенно , поскольку всю страницу можно восстановить из памяти, не прибегая к сети. |
Посмотрите это видео, демонстрирующее работу bfcache, чтобы понять, насколько он может ускорить навигацию:
В видеоролике пример с использованием bfcache показывает значительно более высокую скорость, чем пример без него.
bfcache не только ускоряет навигацию, но и снижает потребление данных, поскольку ресурсы не нужно загружать заново.
Данные об использовании Chrome показывают, что каждая десятая навигация на настольных компьютерах и каждая пятая на мобильных устройствах — это переход назад или вперед. С включенным bfcache браузеры могли бы исключить передачу данных и время, затрачиваемое на загрузку миллиардов веб-страниц каждый день!
Как работает "кэш"
Кэш, используемый bfcache, отличается от HTTP-кэша , который играет свою роль в ускорении повторных посещений. bfcache представляет собой снимок всей страницы в памяти, включая кучу JavaScript, тогда как HTTP-кэш содержит только ответы на ранее сделанные запросы. Поскольку крайне редко все запросы, необходимые для загрузки страницы, выполняются из HTTP-кэша, повторные посещения с использованием восстановления из bfcache всегда быстрее, чем даже самые оптимизированные посещения без bfcache.
Замораживание страницы с целью ее последующего повторного включения сопряжено с определенными сложностями в плане наилучшего сохранения разрабатываемого кода. Например, как обрабатывать вызовы setTimeout() когда истекает время ожидания, а страница находится в bfcache?
Ответ заключается в том, что браузеры приостанавливают все ожидающие таймеры или невыполненные промисы для страниц в bfcache, включая почти все ожидающие задачи в очередях задач JavaScript , и возобновляют обработку задач, если страница восстанавливается из bfcache.
В некоторых случаях, например, при использовании таймаутов и промисов, это сопряжено с относительно низким риском, но в других случаях может привести к запутанному или неожиданному поведению. Например, если браузер приостанавливает задачу, необходимую в рамках транзакции IndexedDB , это может повлиять на другие открытые вкладки в том же источнике, поскольку к одним и тем же базам данных IndexedDB могут одновременно обращаться несколько вкладок. В результате браузеры, как правило, не пытаются кэшировать страницы в середине транзакции IndexedDB или при использовании API, которые могут повлиять на другие страницы.
Для получения более подробной информации о том, как использование различных API влияет на возможность использования bfcache для страницы, см. раздел «Оптимизация страниц для bfcache» .
bfcache и iframe
Если страница содержит встроенные iframe-элементы, то сами iframe-элементы не могут быть отдельно включены в bfcache. Например, если вы переходите на другой URL-адрес внутри iframe, предыдущий контент не попадает в bfcache, и если вы возвращаетесь назад, браузер переходит «назад» внутри iframe, а не в основной фрейм, но навигация назад внутри iframe не будет использовать bfcache.
Однако, когда основной фрейм восстанавливается из bfcache, встроенные iframe будут восстановлены в том виде, в котором они были на момент добавления страницы в bfcache.
Использование bfcache на главном фрейме также может быть заблокировано, если встроенный iframe использует API, которые это предотвращают. Для этого можно использовать политику разрешений, установленную на главном фрейме, или атрибуты sandbox .
bfcache и одностраничные приложения (SPA)
Поскольку bfcache работает с навигацией, управляемой браузером, он не подходит для «мягкой навигации» внутри одностраничного приложения (SPA). Однако bfcache все же может помочь, если вы хотите вернуться к SPA, а не выполнять полную переинициализацию приложения с нуля.
API для наблюдения за bfcache
Хотя bfcache — это оптимизация, которую браузеры выполняют автоматически, разработчикам все же важно знать, когда она происходит, чтобы они могли оптимизировать свои страницы и соответствующим образом корректировать любые метрики или измерения производительности .
Основными событиями, используемыми для отслеживания bfcache, являются события перехода между страницами pageshow и pagehide , которые поддерживаются большинством браузеров .
Новые события жизненного цикла страницы — freeze и resume — также отправляются при входе или выходе страниц из bfcache, а также в некоторых других ситуациях, например, когда фоновая вкладка замораживается для минимизации использования ЦП. Эти события поддерживаются только в браузерах на основе Chromium.
Обратите внимание, когда страница восстанавливается из bfcache.
Событие pageshow срабатывает сразу после события load при первоначальной загрузке страницы и всякий раз, когда страница восстанавливается из bfcache. Событие pageshow имеет свойство persisted , которое принимает значение true если страница была восстановлена из bfcache, и 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.');
}
});
В браузерах, поддерживающих API жизненного цикла страницы, событие resume срабатывает при восстановлении страниц из bfcache (непосредственно перед событием pageshow ) и при повторном посещении пользователем заблокированной фоновой вкладки. Если вы хотите обновить состояние страницы после ее блокировки (включая страницы в bfcache), вы можете использовать событие resume , но если вы хотите измерить частоту обращений к bfcache вашего сайта, вам потребуется использовать событие pageshow . В некоторых случаях может потребоваться использовать оба события.
Подробную информацию о лучших практиках измерения производительности с помощью bfcache см. в разделе «Как bfcache влияет на аналитику и измерение производительности» .
Обратите внимание, когда страница обращается к bfcache.
Событие pagehide срабатывает либо при выгрузке страницы, либо когда браузер пытается поместить её в bfcache.
Событие pagehide также имеет свойство persisted . Если оно имеет false , вы можете быть уверены, что страница не будет помещена в bfcache. Однако значение persisted , равное true , не гарантирует кэширование страницы. Это означает, что браузер намерен кэшировать страницу, но могут быть другие факторы, которые делают это невозможным.
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 , но это лишь означает, что браузер намерен кэшировать страницу. Ему все равно может потребоваться удалить ее по ряду причин, которые будут объяснены позже.
Оптимизируйте свои страницы для bfcache.
Не все страницы сохраняются в bfcache, и даже если страница там и находится, она не остаётся там бесконечно. Крайне важно, чтобы разработчики понимали, какие страницы подходят (и не подходят) для bfcache, чтобы максимизировать показатели попадания в кэш.
В следующих разделах описаны лучшие практики, позволяющие максимально повысить вероятность кэширования ваших страниц браузером.
Никогда не используйте событие unload .
Самый важный способ оптимизации для bfcache во всех браузерах — никогда не использовать событие unload . Никогда!
Событие unload создает проблемы для браузеров, поскольку оно существовало до появления bfcache, и многие страницы в интернете работают, исходя из (разумного) предположения, что страница перестанет существовать после срабатывания события unload . Это создает проблему, поскольку многие из этих страниц также были созданы с предположением, что событие unload будет срабатывать всякий раз, когда пользователь переходит на другую страницу, что уже не соответствует действительности (и уже давно не соответствует ).
Таким образом, перед разработчиками браузеров встает дилемма: им нужно выбрать между чем-то, что может улучшить пользовательский опыт, но при этом может привести к сбою страницы.
В настольных версиях Chrome и Firefox страницы, добавленные с обработчиком события unload listener), не подлежат кэшированию через bfcache. Это менее рискованно, но также исключает из кэширования множество страниц. Safari попытается кэшировать некоторые страницы с обработчиком события unload , но для уменьшения потенциальных проблем не будет запускать событие unload при переходе пользователя на другую страницу, что делает это событие очень ненадежным.
На мобильных устройствах Chrome и Safari будут пытаться кэшировать страницы с обработчиком события unload поскольку риск возникновения проблем ниже из-за того, что событие unload всегда было крайне ненадежным на мобильных устройствах. Firefox считает страницы, использующие unload , непригодными для bfcache, за исключением iOS, где все браузеры используют механизм рендеринга WebKit и, следовательно, ведут себя как Safari.
Вместо события unload используйте событие pagehide . Событие pagehide срабатывает во всех случаях, когда срабатывает событие unload , а также при добавлении страницы в bfcache.
На самом деле, Lighthouse имеет функцию аудита no-unload-listeners , которая предупреждает разработчиков, если какой-либо JavaScript на их страницах (включая код из сторонних библиотек) добавляет обработчик события unload .
Из-за своей ненадежности и влияния на производительность bfcache, Chrome планирует отказаться от события unload .
Используйте политику разрешений, чтобы запретить использование обработчиков выгрузки на странице.
Сайты, которые не используют обработчики событий unload могут предотвратить их добавление с помощью политики разрешений .
Permissions-Policy: unload=()
Это также предотвращает замедление работы сайта третьими лицами или расширениями путем добавления обработчиков выгрузки и, как следствие, невозможности использования bfcache для сайта.
Добавляйте обработчики событий beforeunload только при определенных условиях.
Событие beforeunload не сделает ваши страницы непригодными для кэширования в современных браузерах, но раньше это происходило, и оно по-прежнему ненадежно, поэтому избегайте его использования, если это не абсолютно необходимо.
В отличие от события unload , у beforeunload есть законные применения. Например, когда вы хотите предупредить пользователя о наличии несохраненных изменений, которые он потеряет, если покинет страницу. В этом случае рекомендуется добавлять обработчики beforeunload только тогда, когда у пользователя есть несохраненные изменения, и удалять их сразу после сохранения этих изменений.
window.addEventListener('beforeunload', (event) => { if (pageHasUnsavedChanges()) { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?'; } });
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); });
beforeunload только тогда, когда это необходимо (и удаляет его, когда это не требуется). Сведите к минимуму использование Cache-Control: no-store
Cache-Control: no-store — это HTTP-заголовок, который веб-серверы могут устанавливать в ответах, чтобы указать браузеру не сохранять ответ в HTTP-кэше. Он используется для ресурсов, содержащих конфиденциальную информацию о пользователе, таких как страницы, доступные только после авторизации.
Хотя bfcache не является HTTP-кэшем, исторически сложилось так, что когда Cache-Control: no-store установлен для самого ресурса страницы (а не для какого-либо подресурса), браузеры предпочитали не сохранять страницу в bfcache, поэтому любые страницы, использующие Cache-Control: no-store могли не подходить для кэширования в bfcache. В настоящее время ведётся работа по изменению этого поведения в Chrome с учётом требований конфиденциальности.
Поскольку Cache-Control: no-store ограничивает возможность использования bfcache для страницы, его следует устанавливать только для страниц, содержащих конфиденциальную информацию, где кэширование любого рода никогда нецелесообразно.
Для страниц, которым необходимо всегда отображать актуальный контент, и этот контент не содержит конфиденциальной информации, используйте Cache-Control: no-cache или Cache-Control: max-age=0 . Эти директивы указывают браузеру на необходимость повторной проверки контента перед его отображением и не влияют на возможность использования bfcache для страницы.
Обратите внимание, что при восстановлении страницы из bfcache она восстанавливается из памяти, а не из HTTP-кэша. В результате директивы типа Cache-Control: no-cache или Cache-Control: max-age=0 не учитываются, и повторная проверка перед отображением контента пользователю не происходит.
Однако, это, вероятно, все же обеспечивает лучший пользовательский опыт, поскольку восстановление bfcache происходит мгновенно, и — так как страницы не остаются в bfcache очень долго — маловероятно, что контент устареет. Тем не менее, если ваш контент меняется каждую минуту, вы можете получить любые обновления, используя событие pageshow , как описано в следующем разделе.
Обновите устаревшие или конфиденциальные данные после восстановления bfcache.
Если ваш сайт хранит состояние пользователя, особенно конфиденциальную информацию, эти данные необходимо обновлять или очищать после восстановления страницы из bfcache.
Например, если пользователь переходит на страницу оформления заказа, а затем обновляет свою корзину, возврат назад может привести к отображению устаревшей информации, если из bfcache будет восстановлена устаревшая страница.
Другой, более важный пример: если пользователь выходит из системы на общедоступном компьютере, а следующий пользователь нажимает кнопку «Назад», это потенциально может привести к утечке личных данных, которые, как предполагал пользователь, были удалены при выходе из системы.
Чтобы избежать подобных ситуаций, рекомендуется всегда обновлять страницу после события pageshow , если event.persisted имеет true :
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// Do any checks and updates to the page
}
});
В идеале следует обновлять контент на месте, но для некоторых изменений может потребоваться полная перезагрузка страницы. Следующий код проверяет наличие cookie-файла, специфичного для данного сайта, в событии pageshow и перезагружает страницу, если 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, обновление только рекламы в событии pageshow , когда event.persisted имеет значение true , позволяет это сделать без влияния на производительность страницы. Уточните у своего поставщика рекламы, но вот один из примеров того, как это сделать с помощью тега публикации Google .
Избегайте ссылок window.opener
В старых браузерах, если страница открывалась с помощью window.open() по ссылке с target=_blank , без указания rel="noopener" , открывающаяся страница имела бы ссылку на объект window открытой страницы.
Помимо того, что это представляет угрозу безопасности , страницу с ненулевой ссылкой window.opener нельзя безопасно поместить в bfcache, поскольку это может нарушить работу любых страниц, пытающихся получить к ней доступ.
В результате лучше избегать создания ссылок window.opener . Это можно сделать, используя rel="noopener" везде, где это возможно (обратите внимание, что это теперь значение по умолчанию во всех современных браузерах). Если ваш сайт требует открытия окна и управления им через window.postMessage() или путем прямой ссылки на объект window, ни открытое окно, ни открывающий объект не будут доступны для bfcache.
Закрывайте открытые соединения перед тем, как пользователь покинет сайт.
Как уже упоминалось ранее, когда страница находится в bfcache, все запланированные задачи JavaScript приостанавливаются и возобновляются, когда страница извлекается из кэша.
Если эти запланированные задачи JavaScript обращаются только к API DOM — или к другим API, доступным только на текущей странице, — то приостановка этих задач, когда страница не видна пользователю, не вызовет никаких проблем.
Однако, если эти задачи связаны с API, доступными также с других страниц того же источника (например, IndexedDB, Web Locks, WebSockets), это может создать проблемы, поскольку приостановка этих задач может помешать выполнению кода в других вкладках.
В результате, в следующих сценариях некоторые браузеры не будут пытаться поместить страницу в bfcache:
- Страницы с открытым соединением с IndexedDB .
- Страницы, на которых выполняется запрос fetch() или XMLHttpRequest .
- Страницы с открытым соединением WebSocket или WebRTC . Chrome (начиная с версии 149) и Safari не блокируют открытые соединения WebSocket, но другие браузеры блокируют.
Если ваша страница использует какой-либо из этих API, мы настоятельно рекомендуем закрывать соединения и удалять или отключать наблюдателей во время события скрытия или freeze pagehide . Это позволит браузеру безопасно кэшировать страницу без риска повлиять на другие открытые вкладки.
Затем, если страница будет восстановлена из bfcache, вы можете повторно открыть или подключиться к этим API во время событий pageshow или resume , или же настроить их автоматическое повторное открытие с помощью событий error или close . Убедитесь, что вы не открываете несколько соединений, если используете несколько событий.
В следующем примере показано, как обеспечить возможность кэширования 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;
}
// 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 помогут вам протестировать ваши страницы, чтобы убедиться в их оптимизации для bfcache, а также выявить любые проблемы, которые могут помешать им соответствовать требованиям.
Для тестирования страницы:
- Перейдите на страницу в Chrome.
- В инструментах разработчика перейдите в раздел «Приложение» -> «Кэш перемотки вперед-назад» .
- Нажмите кнопку «Запустить тест» . Затем DevTools попытается перейти на другую страницу и вернуться обратно, чтобы определить, можно ли восстановить страницу из 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.
// This happens by default just by loading gtag
gtag('config', 'TAG_ID');
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.
// To do this disable the default pageview so you can manually send it
// supplemented with the additional detail.
gtag('config', 'TAG_ID', { send_page_view: false });
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';
});
}
});
Рассчитайте коэффициент попаданий в bfcache, используя количество переходов back_forward , а также количество переходов back_forward_cache кэше.
Важно понимать, что существует ряд сценариев, не зависящих от владельцев сайта, когда навигация «Назад/Вперед» не будет использовать bfcache, в том числе:
- когда пользователь закрывает браузер и запускает его снова
- когда пользователь дублирует вкладку
- когда пользователь закрывает вкладку и открывает её снова
В некоторых из этих случаев исходный тип навигации может сохраняться некоторыми браузерами, и поэтому может отображаться тип «назад/ back_forward несмотря на то, что это не навигация «Назад/Вперед».
Даже без этих исключений кэш bfcache будет удален через некоторое время для экономии памяти.
Таким образом, владельцам веб-сайтов не следует ожидать 100% использования bfcache для всех переходов back_forward . Однако измерение этого показателя может быть полезно для выявления страниц, где сама страница препятствует использованию bfcache для значительной доли переходов назад и вперед.
Команда Chrome добавила API NotRestoredReasons , чтобы помочь выявить причины, по которым страницы не используют bfcache, и тем самым повысить коэффициент попаданий в bfcache. Команда Chrome также добавила в CrUX типы навигации , позволяющие увидеть количество переходов по ссылкам bfcache даже без самостоятельных измерений.
Измерение производительности
bfcache также может негативно влиять на показатели производительности, собираемые в полевых условиях , в частности, на показатели, измеряющие время загрузки страниц.
Поскольку навигация с использованием bfcache восстанавливает существующую страницу, а не инициирует новую загрузку страницы, общее количество загрузок страниц уменьшится при включении bfcache. Однако важно отметить, что загрузки страниц, заменяемые восстановлением с помощью bfcache, скорее всего, были одними из самых быстрых в вашем наборе данных. Это связано с тем, что переходы «назад» и «вперед» по определению являются повторными посещениями, а повторные загрузки страниц, как правило, быстрее, чем загрузки страниц от посетителей, посетивших сайт впервые (благодаря HTTP-кэшированию , как упоминалось ранее).
В результате в вашем наборе данных будет меньше быстрых загрузок страниц, что, вероятно, приведет к замедлению распределения — несмотря на то, что производительность для пользователя, скорее всего, улучшится!
Существует несколько способов решения этой проблемы. Один из них — аннотировать все метрики загрузки страницы соответствующим типом навигации : navigate , reload , back_forward или prerender . Это позволяет продолжать отслеживать производительность в рамках этих типов навигации, даже если общее распределение имеет отрицательный уклон. Мы рекомендуем этот подход для метрик загрузки страницы, не ориентированных на пользователя, таких как время до первого байта (TTFB) .
Для ориентированных на пользователя метрик, таких как Core Web Vitals , лучше указывать значение, которое более точно отражает то, что испытывает пользователь.
Влияние на основные показатели веб-инфраструктуры
Показатели Core Web Vitals измеряют удобство использования веб-страницы для пользователя по различным параметрам (скорость загрузки, интерактивность, визуальная стабильность), и поскольку восстановление bfcache воспринимается пользователями как более быстрая навигация, чем полная загрузка страницы, важно, чтобы метрики Core Web Vitals это отражали. В конце концов, пользователю всё равно, был ли включен bfcache или нет, ему важно лишь, чтобы навигация была быстрой!
Инструменты, которые собирают и предоставляют отчеты по метрикам Core Web Vitals, такие как отчет Chrome User Experience Report , рассматривают восстановление bfcache как отдельные посещения страниц в своем наборе данных. И хотя специальных API для измерения этих метрик после восстановления bfcache не существует, вы можете приблизительно оценить их значения, используя существующие веб-API:
- Для параметра Largest Contentful Paint (LCP) используйте разницу между временной меткой события
pageshowи временной меткой следующего отрисованного кадра, поскольку все элементы в кадре будут отрисованы одновременно. В случае восстановления bfcache параметры LCP и FCP совпадают. - Для параметра «Взаимодействие с последующим отрисовыванием» (INP) продолжайте использовать существующий Performance Observer, но сбросьте текущее значение INP до 0.
- Для параметра Cumulative Layout Shift (CLS) продолжайте использовать существующий Performance Observer, но установите текущее значение CLS на 0.
Более подробную информацию о влиянии bfcache на каждую метрику см. на страницах руководств по метрикам Core Web Vitals. Конкретный пример реализации версий этих метрик с использованием bfcache см. в запросе на добавление их в библиотеку JavaScript web-vitals .
Библиотека JavaScript web-vitals поддерживает восстановление bfcache в сообщаемых ею метриках.
Дополнительные ресурсы
- Кэширование в Firefox (bfcache в Firefox)
- Кэш страниц (bfcache в Safari)
- Кэш "назад/вперед": поведение, доступное через веб-интерфейс (различия в работе bfcache в разных браузерах)
- Тестер bfcache (проверка влияния различных API и событий на работу bfcache в браузерах)
- Революционное улучшение производительности: кэширование «Назад/Вперед» в браузере (тематическое исследование от Smashing Magazine, демонстрирующее значительное улучшение показателей Core Web Vitals благодаря включению bfcache).
Опубликовано: 25 мая 2023 г., Последнее обновление: 2 июля 2026 г.
Кэширование "назад/вперед" (или bfcache) — это оптимизация браузера, обеспечивающая мгновенную навигацию назад и вперед. Она значительно улучшает работу в браузере, особенно для пользователей с медленным интернетом или устройствами.
Для веб-разработчиков крайне важно понимать, как оптимизировать страницы для bfcache , чтобы пользователи могли получить от этого максимальную выгоду.
Совместимость с браузерами
Все основные браузеры включают bfcache, в том числе Chrome (начиная с версии 96), Firefox и Safari .
основы bfcache
При использовании кэша "назад/вперед" (bfcache) вместо удаления страницы при переходе пользователя на другую страницу, мы откладываем удаление и приостанавливаем выполнение JavaScript. Если пользователь вскоре вернется на предыдущую страницу, мы снова сделаем ее видимой и возобновим выполнение JavaScript. Это приводит к практически мгновенной навигации по страницам для пользователя.
Сколько раз вы заходили на сайт, переходили по ссылке на другую страницу, а потом понимали, что это не то, что вам нужно, и нажимали кнопку «Назад»? В такой ситуации bfcache может существенно ускорить загрузку предыдущей страницы:
| Без включенного bfcache | Инициируется новый запрос для загрузки предыдущей страницы, и, в зависимости от того, насколько хорошо эта страница оптимизирована для повторных посещений, браузеру может потребоваться повторно загрузить, повторно проанализировать и повторно выполнить некоторые (или все) ресурсы, которые он только что загрузил. |
| При включенном bfcache | Загрузка предыдущей страницы происходит практически мгновенно , поскольку всю страницу можно восстановить из памяти, не прибегая к сети. |
Посмотрите это видео, демонстрирующее работу bfcache, чтобы понять, насколько он может ускорить навигацию:
В видеоролике пример с использованием bfcache показывает значительно более высокую скорость, чем пример без него.
bfcache не только ускоряет навигацию, но и снижает потребление данных, поскольку ресурсы не нужно загружать заново.
Данные об использовании Chrome показывают, что каждая десятая навигация на настольных компьютерах и каждая пятая на мобильных устройствах — это переход назад или вперед. С включенным bfcache браузеры могли бы исключить передачу данных и время, затрачиваемое на загрузку миллиардов веб-страниц каждый день!
Как работает "кэш"
Кэш, используемый bfcache, отличается от HTTP-кэша , который играет свою роль в ускорении повторных посещений. bfcache представляет собой снимок всей страницы в памяти, включая кучу JavaScript, тогда как HTTP-кэш содержит только ответы на ранее сделанные запросы. Поскольку крайне редко все запросы, необходимые для загрузки страницы, выполняются из HTTP-кэша, повторные посещения с использованием восстановления из bfcache всегда быстрее, чем даже самые оптимизированные посещения без bfcache.
Замораживание страницы с целью ее последующего повторного включения сопряжено с определенными сложностями в плане наилучшего сохранения разрабатываемого кода. Например, как обрабатывать вызовы setTimeout() когда истекает время ожидания, а страница находится в bfcache?
Ответ заключается в том, что браузеры приостанавливают все ожидающие таймеры или невыполненные промисы для страниц в bfcache, включая почти все ожидающие задачи в очередях задач JavaScript , и возобновляют обработку задач, если страница восстанавливается из bfcache.
В некоторых случаях, например, при использовании таймаутов и промисов, это сопряжено с относительно низким риском, но в других случаях может привести к запутанному или неожиданному поведению. Например, если браузер приостанавливает задачу, необходимую в рамках транзакции IndexedDB , это может повлиять на другие открытые вкладки в том же источнике, поскольку к одним и тем же базам данных IndexedDB могут одновременно обращаться несколько вкладок. В результате браузеры, как правило, не пытаются кэшировать страницы в середине транзакции IndexedDB или при использовании API, которые могут повлиять на другие страницы.
Для получения более подробной информации о том, как использование различных API влияет на возможность использования bfcache для страницы, см. раздел «Оптимизация страниц для bfcache» .
bfcache и iframe
Если страница содержит встроенные iframe-элементы, то сами iframe-элементы не могут быть отдельно включены в bfcache. Например, если вы переходите на другой URL-адрес внутри iframe, предыдущий контент не попадает в bfcache, и если вы возвращаетесь назад, браузер переходит «назад» внутри iframe, а не в основной фрейм, но навигация назад внутри iframe не будет использовать bfcache.
Однако, когда основной фрейм восстанавливается из bfcache, встроенные iframe будут восстановлены в том виде, в котором они были на момент добавления страницы в bfcache.
Использование bfcache на главном фрейме также может быть заблокировано, если встроенный iframe использует API, которые это предотвращают. Для этого можно использовать политику разрешений, установленную на главном фрейме, или атрибуты sandbox .
bfcache и одностраничные приложения (SPA)
Поскольку bfcache работает с навигацией, управляемой браузером, он не подходит для «мягкой навигации» внутри одностраничного приложения (SPA). Однако bfcache все же может помочь, если вы хотите вернуться к SPA, а не выполнять полную переинициализацию приложения с нуля.
API для наблюдения за bfcache
Хотя bfcache — это оптимизация, которую браузеры выполняют автоматически, разработчикам все же важно знать, когда она происходит, чтобы они могли оптимизировать свои страницы и соответствующим образом корректировать любые метрики или измерения производительности .
Основными событиями, используемыми для отслеживания bfcache, являются события перехода между страницами pageshow и pagehide , которые поддерживаются большинством браузеров .
Новые события жизненного цикла страницы — freeze и resume — также отправляются при входе или выходе страниц из bfcache, а также в некоторых других ситуациях, например, когда фоновая вкладка замораживается для минимизации использования ЦП. Эти события поддерживаются только в браузерах на основе Chromium.
Обратите внимание, когда страница восстанавливается из bfcache.
Событие pageshow срабатывает сразу после события load при первоначальной загрузке страницы и всякий раз, когда страница восстанавливается из bfcache. Событие pageshow имеет свойство persisted , которое принимает значение true если страница была восстановлена из bfcache, и 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.');
}
});
В браузерах, поддерживающих API жизненного цикла страницы, событие resume срабатывает при восстановлении страниц из bfcache (непосредственно перед событием pageshow ) и при повторном посещении пользователем заблокированной фоновой вкладки. Если вы хотите обновить состояние страницы после ее блокировки (включая страницы в bfcache), вы можете использовать событие resume , но если вы хотите измерить частоту обращений к bfcache вашего сайта, вам потребуется использовать событие pageshow . В некоторых случаях может потребоваться использовать оба события.
Подробную информацию о лучших практиках измерения производительности с помощью bfcache см. в разделе «Как bfcache влияет на аналитику и измерение производительности» .
Обратите внимание, когда страница обращается к bfcache.
Событие pagehide срабатывает либо при выгрузке страницы, либо когда браузер пытается поместить её в bfcache.
The pagehide event also has a persisted property. If it's false , you can be confident a that page isn't about to enter the bfcache. However, persisted being true doesn't guarantee that a page will be cached. It means the browser intends to cache the page, but there may be other factors that make it impossible to cache.
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.');
}
});
Similarly, the freeze event fires immediately after the pagehide event if persisted is true , but that only means the browser intends to cache the page. It might still have to discard it for a number of reasons explained later.
Optimize your pages for bfcache
Not all pages get stored in bfcache, and even when a page does get stored there, it won't stay there indefinitely. It's critical that developers understand what makes pages eligible (and ineligible) for bfcache to maximize their cache-hit rates.
The following sections outline the best practices to make it as likely as possible that the browser can cache your pages.
Never use the unload event
The most important way to optimize for bfcache in all browsers is to never use the unload event. Ever!
The unload event is problematic for browsers because it predates bfcache and many pages on the internet operate under the (reasonable) assumption that a page won't continue to exist after the unload event has fired. This presents a challenge because many of those pages were also built with the assumption that the unload event would fire any time a user is navigating away, which is no longer true (and hasn't been true for a long time ).
So browsers are faced with a dilemma, they have to choose between something that can improve the user experience—but might also risk breaking the page.
On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an unload listener, which is less risky but also disqualifies a lot of pages. Safari will attempt to cache some pages with an unload event listener, but to reduce potential breakage it won't run the unload event when a user is navigating away, which makes the event very unreliable.
On mobile, Chrome and Safari will attempt to cache pages with an unload event listener since the risk of breakage is lower due to the fact that the unload event has always been extremely unreliable on mobile. Firefox treats pages that use unload as ineligible for the bfcache, except on iOS, where all browsers use the WebKit rendering engine, and so behave like Safari.
Instead of using the unload event, use the pagehide event. The pagehide event fires in all cases where the unload event fires, and it also fires when a page is put in the bfcache.
In fact, Lighthouse has a no-unload-listeners audit , which will warn developers if any JavaScript on their pages (including that from third-party libraries) adds an unload event listener.
Due to its unreliability, and the performance impact for bfcache, Chrome is looking to deprecate the unload event .
Use Permission Policy to prevent unload handlers being used on a page
Sites that don't use unload event handlers can ensure these are not added by using a Permissions Policy .
Permissions-Policy: unload=()
This also prevents third parties or extensions from slowing the site down by adding unload handlers and making the site ineligible for the bfcache.
Only add beforeunload listeners conditionally
The beforeunload event won't make your pages ineligible for bfcache in modern browsers' bfcache but previously it did and it is still unreliable, so avoid using it unless absolutely necessary.
Unlike the unload event, however, there are legitimate uses for beforeunload . For example, when you want to warn the user that they have unsaved changes they'll lose if they leave the page. In this case, it's recommended that you only add beforeunload listeners when a user has unsaved changes and then remove them immediately after the unsaved changes are saved.
window.addEventListener('beforeunload', (event) => { if (pageHasUnsavedChanges()) { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?'; } });
beforeunload listener unconditionally.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); });
beforeunload listener when it's needed (and removes it when it's not). Minimize use of Cache-Control: no-store
Cache-Control: no-store is an HTTP header web servers can set on responses that instructs the browser not to store the response in any HTTP cache. It's used for resources containing sensitive user information, such as pages behind a login.
Although bfcache is not an HTTP cache, historically, when Cache-Control: no-store is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache so any pages using Cache-Control: no-store may not be eligible for bfcache. There is work underway to change this behavior for Chrome in a privacy-preserving manner.
Since Cache-Control: no-store restricts a page's eligibility for bfcache, it should only be set on pages that contain sensitive information where caching of any sort is never appropriate.
For pages that need to always serve up-to-date content—and that content does not contain sensitive information—use Cache-Control: no-cache or Cache-Control: max-age=0 . These directives instruct the browser to revalidate the content before serving it, and they don't affect a page's bfcache eligibility.
Note that when a page is restored from bfcache, it is restored from memory, not from the HTTP cache. As a result, directives like Cache-Control: no-cache or Cache-Control: max-age=0 are not taken into account, and no revalidation occurs before the content is displayed to the user.
This is still likely a better user experience, however, as bfcache restores are instant and—since pages don't stay in the bfcache for very long—it's unlikely that the content is out of date. However, if your content does change minute-by-minute, you can fetch any updates using the pageshow event, as outlined in the next section.
Update stale or sensitive data after bfcache restore
If your site keeps user state—especially any sensitive user information—that data needs to be updated or cleared after a page is restored from bfcache.
For example, if a user navigates to a checkout page and then updates their shopping cart, a back navigation could potentially expose out-of-date information if a stale page is restored from bfcache.
Another, more critical example is if a user signs out of a site on a public computer and the next user clicks the back button. This could potentially expose private data that the user assumed was cleared when they logged out.
To avoid situations like this, it's good to always update the page after a pageshow event if event.persisted is true :
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// Do any checks and updates to the page
}
});
While ideally you would update the content in place, for some changes you may want to force a full reload. The following code checks for the presence of a site-specific cookie in the pageshow event and reloads if the cookie is not found:
window.addEventListener('pageshow', (event) => {
if (event.persisted && !document.cookie.match(/my-cookie)) {
// Force a reload if the user has logged out.
location.reload();
}
});
A reload has the advantage that will still preserve the history (to allow forward navigations), but a redirect may be more appropriate in some cases.
Ads and bfcache restore
It may be tempting to try to avoid the use of bfcache to serve a new set of ads on each back/forward navigation. However, as well as having a performance impact, it is questionable whether such behavior leads to better ad engagement. Users may have noticed an ad they intended to return to click but by reloading rather than restoring from the bfcache they not be able to. Testing this scenario—ideally with an A/B test—is important before making assumptions.
For sites that do want to refresh ads on bfcache restore, then refreshing just the ads on the pageshow event when event.persisted is true allows this to happen without impacting the page performance. Check with your ad provider but here is one example on how to do this with Google Publishing Tag .
Avoid window.opener references
In older browsers, if a page was opened using window.open() from a link with target=_blank , without specifying rel="noopener" , the opening page would have a reference to the window object of the opened page.
In addition to being a security risk , a page with a non-null window.opener reference can't safely be put into bfcache, because that could break any pages attempting to access it.
As a result, it's best to avoid creating window.opener references. You can do this by using rel="noopener" whenever possible (note, this is now the default in all modern browsers). If your site requires opening a window and controlling it through window.postMessage() or directly referencing the window object, neither the opened window nor the opener will be eligible for the bfcache.
Close open connections before the user navigates away
As mentioned previously, when a page is held in the bfcache, it pauses all scheduled JavaScript tasks and resumes them when the page is taken out of the cache.
If these scheduled JavaScript tasks are only accessing DOM APIs—or other APIs isolated to just the current page—then pausing these tasks while the page is not visible to the user is not going to cause any problems.
However, if these tasks are connected to APIs that are also accessible from other pages in the same origin (for example: IndexedDB, Web Locks, WebSockets) this can be problematic because pausing these tasks may prevent code in other tabs from running.
As a result, some browsers won't attempt to put a page in bfcache in the following scenarios:
- Pages with an open IndexedDB connection .
- Pages with in-progress fetch() or XMLHttpRequest .
- Pages with an open WebSocket or WebRTC connection. Chrome (as of 149) and Safari do no block on open WebSockets but other browsers do.
If your page is using any of these APIs, we strongly recommend closing connections and removing or disconnecting observers during the pagehide or freeze event. That allows the browser to safely cache the page without the risk of affecting other open tabs.
Then, if the page is restored from the bfcache, you can reopen or reconnect to those APIs during the pageshow or resume event, or have them always automatically reopen using the error or close events. Ensure you do no open multiple connections if using multiple events.
The following example shows how to ensure that pages using IndexedDB are eligible for bfcache by closing an open connection in the pagehide event listener:
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());
Test to ensure your pages are cacheable
Chrome DevTools can help you test your pages to ensure they're optimized for bfcache, and identify any issues that might prevent them from being eligible.
To test a page:
- Navigate to the page in Chrome.
- In DevTools, go to Application -> Back-forward Cache .
- Click the Run Test button. DevTools then tries to navigate away and back to determine whether the page can be restored from bfcache.

If the test is successful, the panel reports "Restored from back-forward cache".

If it's unsuccessful, the panel indicates the reason why. If the reason is something you can address as a developer, the panel marks it as Actionable .

In this example, the use of an unload event listener makes the page ineligible for bfcache. You can fix that by switching from unload to using pagehide :
window.addEventListener('pagehide', ...);
window.addEventListener('unload', ...);
Lighthouse 10.0 also added a bfcache audit , which performs a similar test. For more information, see the bfcache audit's docs .
How bfcache affects analytics and performance measurement
If you use an analytics tool to measure visits to your site, you might notice a decrease in the total number of pageviews reported as Chrome enables bfcache for more users.
In fact, you're likely already underreporting pageviews from other browsers that implement bfcache, because many popular analytics libraries don't measure bfcache restores as new pageviews.
To include bfcache restores in your pageview count, set listeners for the pageshow event and check the persisted property.
The following example shows how to do this with Google Analytics. Other analytics tools likely use similar logic:
// Send a pageview when the page is first loaded.
// This happens by default just by loading gtag
gtag('config', 'TAG_ID');
window.addEventListener('pageshow', (event) => {
// Send another pageview if the page is restored from bfcache.
if (event.persisted) {
gtag('event', 'page_view');
}
});
Measure your bfcache hit ratio
You may also want to measure whether the bfcache was used, to help identify pages that are not utilizing the bfcache. This can be done by measuring the navigation type for page loads:
// Send a navigation_type when the page is first loaded.
// To do this disable the default pageview so you can manually send it
// supplemented with the additional detail.
gtag('config', 'TAG_ID', { send_page_view: false });
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';
});
}
});
Calculate your bfcache hit ratio using the counts for back_forward navigations and back_forward_cache navigations.
It is important to realize that there are a number of scenarios, outside of the site owners control, when a Back/Forward navigation won't use the bfcache, including:
- when the user quits the browser and starts it again
- when the user duplicates a tab
- when the user closes a tab and reopens it
In some of these cases the original navigation type may be preserved by some browsers and so may show a type of back_forward despite these not being Back/Forward navigations.
Even without those exclusions the bfcache will be discarded after a period to conserve memory.
So, website owners shouldn't be expecting a 100% bfcache hit ratio for all back_forward navigations. However, measuring their ratio can be useful to identify pages where the page itself is preventing bfcache usage for a high proportion of back and forward navigations.
The Chrome team has added the NotRestoredReasons API to help expose the reasons why pages don't use bfcache, so developers can improve their bfcache hit rates. The Chrome team has also added navigation types to CrUX making it possible to see the number of bfcache navigations even without measuring it yourself.
Измерение производительности
bfcache can also negatively affect performance metrics collected in the field , specifically metrics that measure page load times.
Since bfcache navigations restore an existing page rather than initiate a new page load, the total number of page loads collected will decrease when bfcache is enabled. What's critical, though, is that the page loads being replaced by bfcache restores would likely have been some of the fastest page loads in your dataset. This is because back and forward navigations, by definition, are repeat visits, and repeat page loads are generally faster than page loads from first time visitors (due to HTTP caching , as mentioned earlier).
The result is fewer fast page loads in your dataset, which will likely skew the distribution slower—despite the fact that the performance experienced by the user has probably improved!
There are a few ways to deal with this issue. One is to annotate all page load metrics with their respective navigation type : navigate , reload , back_forward , or prerender . This lets you continue to monitor your performance within these navigation types, even if the overall distribution skews negative. We recommend this approach for non-user-centric page load metrics like Time to First Byte (TTFB) .
For user-centric metrics like the Core Web Vitals , a better option is to report a value that more accurately represents what the user experiences.
Impact on Core Web Vitals
Core Web Vitals measure the user's experience of a web page across a variety of dimensions (loading speed, interactivity, visual stability), and since users experience bfcache restores as faster navigations than full page loads, it's important that the Core Web Vitals metrics reflect this. After all, a user doesn't care whether or not bfcache was enabled, they just care that the navigation was fast!
Tools that collect and report on the Core Web Vitals metrics, like the Chrome User Experience Report , treat bfcache restores as separate page visits in their dataset. And while there aren't dedicated web performance APIs for measuring these metrics after bfcache restores, you can approximate their values using existing web APIs:
- For Largest Contentful Paint (LCP) , use the delta between the
pageshowevent's timestamp and the timestamp of the next painted frame, because all elements in the frame will be painted at the same time. In the case of a bfcache restore, LCP and FCP are the same. - For Interaction to Next Paint (INP) , keep using your existing Performance Observer, but reset the current INP value to 0.
- For Cumulative Layout Shift (CLS) , keep using your existing Performance Observer, but reset the current CLS value to 0.
For more details on how bfcache affects each metric, see the individual Core Web Vitals metric guides pages . For a specific example of how to implement bfcache versions of these metrics, refer to the PR adding them to the web-vitals JS library .
The web-vitals JavaScript library supports bfcache restores in the metrics it reports.
Дополнительные ресурсы
- Firefox Caching (bfcache in Firefox)
- Page Cache (bfcache in Safari)
- Back/forward cache: web exposed behavior (bfcache differences across browsers)
- bfcache tester (test how different APIs and events affect bfcache in browsers)
- Performance Game Changer: Browser Back/Forward Cache (a case study from Smashing Magazine showing dramatic Core Web Vitals improvements by enabling bfcache)