bfcache: Improve Back & Forward Page Views

TOC

What is bfcache?

Back/forward cache (or bfcache) is a browser optimization that enables instant back and forward navigation.
https://web.dev/bfcache/

There are two important concepts to know about bfcache right away:

  1. bfcache is not really a “cache”: where a browser cache or CDN cache might temporarily “store” a page’s assets to help them load faster in the future, bfcache is a literal snapshot of the entire, fully-rendered page, including all assets, the completed layout and the full JS heap.
  2. bfcache relates solely to pages that have already been visited by the user and therefore can be reached via the browser’s Back and Forward buttons.

To make bfcache work, the browser stores a fully rendered version of visited pages in its cache. Then, when the user clicks the Back or Forward button, simply displaying the cached page to the user, rather than initiating a complete page request again:

bfcache-1-memory
Credit: https://murshidmuzamil.com/blog/bfcache-what-you-need-to-know/

Here is a side-by-side example of loading a page, then hitting the Back, then the Forward, buttons:

YouTube link: https://youtu.be/cuPsdRckkF0

The user experience is markedly improved, allowing for a near-instant Back and Forward page transitions, which allows for a much faster, more enjoyable browsing session.

And this not only makes the page load experience faster, but also:

  • Saves the user’s data plan, because there is no bandwidth required to display the cached page;
  • Saves the user’s battery life, because there is no fetching, parsing, rendering, etc. either;
  • Reduces overall server and CDN requests, because no page assets need to be requested again, and
  • Eliminates any KPI-related issues, because the page is already fully rendered (so, no CLS!).

Even things like an unresolved setTimeout or a Promise will be paused while the page is cached and resumed once the cached page is revisited. (Be aware, however, of possible network-related complications.)

As with all caches, however, there is a maximum cache size, meaning, as the cache fills, older pages will be removed (FIFO).

How do I add bfcache?

bfcache is available by default in all major browsers (Safari > 1.0, Firefox > 1.5 and Chrome > 96).

Unless you are doing something to break it

How do I not break bfcache?

Again, bfcache is enabled by default in all modern browsers, so you don’t have to do anything to add it, but there are a number of blockers that will prevent a page from being cached.

To avoid blocking bfcache:

  • Never use an unload event handler

    • This handler has not been reliable for years, and continues to be problematic today, especially on mobile devices, and so will block a page from being cached.
    • The preferred method is to use the pagehide event handler. This does not prevent bfcache from caching this page.
  • Limit use of the Cache-Control: no-store header

    • This literally tells the browser to not cache this page…
    • no-store should be used only for pages that contain sensitive data, which should not be cached.
    • If the page contains time-sensitive data, something that should be updated or validated, you can use Cache-Control: max-age=0 instead.
    • max-age=0 does not prevent bfcache from caching this page, but you might need to use the pageshow event to refresh the page’s content when the user returns to it.
  • Avoid creating window.opener references

    • If at all possible, do not use window.open() or target="_blank" to open pages.
    • This creates a window.opener reference in the new page, which will block the page from being cached.
    • If you must use either window.open() or target="_blank", then also add a rel="noopener" to prevent the window.opener reference. This does not prevent bfcache from caching this page.
  • Pause all network activity

    • If the page interacts with any network resource after the user navigates away, then the browser cannot know what state it should return to.
    • And if the page interacts with any API, such as IndexedDB, freezing that could harm other pages that need to use it.
    • Close, disconnect or pause any network or API connections when either the pagehide or freeze event fires.
    • This includes any IndexedDB connection, any in-progress fetch() or XMLHttpRequest, or any open WebSocket or WebRTC connection.
    • These can all be resumed if the pageshow or resume event fires.

How can I test & debug bfcache?

Event listeners

bfcache does have an API, which includes the pagehide and pageshow events. (Chrome also offers a Page Lifecycle API, but when this article was written, this is Chrome-only.)

These events can be monitored with something like these events:

/* Add these to a page, go to a new page, then hit your Back/Forward buttons to see the results in your console */
window.addEventListener('pagehide', (event) => {
  console.log('pagehide:', event);
});
window.addEventListener('pageshow', (event) => {
  console.log('pageshow:', event);
});

The event returned by both listeners above will contain, among other properties:

  • type: pagehide | pageshow
  • persisted: true | false
  • timeStamp: when event happened

DevTools

You can easily test bfcache using DevTools:

  1. In Chrome, open the page you want to test.
  2. Open DevTools, go to the Application tab and, below Background Services, click Back/Forward cache, then click the Test back/forward cache button:
    bfcache-3-test
  3. If DevTools found any bfcache errors, it will tell you, but the supporting evidence is not exactly helpful:
    bfcache-4-issues
  4. Knowing that unload handlers are blocking bfcache is helpful, but not giving any guidance as to where the unload handler is added, is not…
  5. This test is still under development, so hopefully it will continue to improve with time.
  6. For now, you would need to investigate and try to find the errors within your own code base.

Lighthouse

Lighthouse can also test bfcache:

  1. In Chrome, open the page you want to test.
  2. Open DevTools, go to the Lighthouse tab, select the settings you want (definitely Navigation and Performance, as well as your choice of Mobile or Desktop), then click the Test back/forward cache button:
    bfcache-5-lighthouse

    Lighthouse recommends using an Incognito window to prevent any other tabs or windows from interfering with IndexedDB.
  3. If Lighthouse found any bfcache errors, they will be found under Diagnostics, inside of an expandable section titled Page prevented back/forward cache restoration, but, like the DevTools bfcache test above, the supporting evidence is not exactly helpful:
    bfcache-6-lighthouse-2
  4. Knowing that unload handlers are blocking bfcache is helpful, but not giving any guidance as to where the unload handler is added, is not…
  5. This test is still under development, so hopefully it will continue to improve with time.
  6. For now, you would need to investigate and try to find the errors within your own code base.

Notes

  1. Limitations/drawbacks of bfcache:
    1. Size: The size of the cache is limited by device memory. When the cache is full, the browser will remove pages that have not been visited recently.
    2. Persistence: When a browser closes, bfcache is cleared.
    3. Security: The cache is not secure. Sensitive information that is stored in bfcache can be accessed by anyone who has access to the device.
  2. Most browsers do not see a bfcache restore as a page view, and therefore most analytics do not see it as a page view.
    1. To record bfcache restores as a page view, you would need to manually trigger this. For GA, you could add something like this:
      window.addEventListener('pageshow', (event) => {
        // Send another pageview if the page is restored from bfcache.
        if (event.persisted) {
          gtag('event', 'page_view');
        }
      });
      
  3. Performance metrics may actually get worse.
    1. This is because, as with page views, performance tools may also not record the new page view.
    2. And since even normal Back/Forward page navigation tend to be faster (because of browser and HTTP cache), if these are no longer being tracked, performance KPIs will lose some “faster” data, which will look like things got worse…
    3. One suggestion is to annotate page load metrics with their navigation type (navigate, reload, back_forward, or prerender).
    4. Then only navigate KPIs should be monitored to get a true data set for the user’s page load experience.

Resources

Happy caching,
Atg

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.