Опубликовано: 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 с помощью параметра Cache-Control: no-store — плохой подход, который ухудшает производительность для ваших пользователей.
Вместо этого вы можете сохранить свои страницы доступными для bfcache и обновлять только объявления. Некоторые библиотеки тегирования рекламы, такие как Google Publisher Tag (GPT) , автоматически обновляют активно просматриваемые рекламные места, когда пользователь возвращается на страницу после восстановления bfcache. Если ваша библиотека тегирования рекламы этого не делает, и вы хотите, чтобы объявления обновлялись, вы можете использовать событие pageshow для обнаружения восстановления bfcache и запуска обновления объявлений.
Избегайте ссылок 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 или