šŸ” The Complete Guide to bfcache - How Browsers Make Navigation Blazing Fast

As web developers, we often focus on optimizing initial page loads, but what happens when users click the back button? Modern browsers employ an ingenious optimization called bfcache (Back/Forward Cache) that makes navigation between previously visited pages nearly instantaneous. Let's dive deep into this performance powerhouse.


Understanding bfcache: The Browser's Time Machine

bfcache acts like a time machine for web pages. When users navigate away from your site, instead of discarding the page, browsers preserve the complete page state—including the JavaScript heap, DOM, scroll position, and even in-progress form data—in memory. This enables three key benefits:

  1. Instantaneous navigation: Pages restore in under 100ms, making back/forward navigation feel nearly instant.
  2. Perfect state preservation: Scroll positions, form inputs, and even paused videos resume exactly as left, providing a seamless user experience.
  3. Resource efficiency: Saves bandwidth and reduces server load by avoiding repeat requests and re-rendering.

Note: bfcache is different from the HTTP cache. While HTTP cache stores resources (like images, scripts, etc.), bfcache stores the entire in-memory state of the page.


How bfcache Works: A Technical Breakdown

The caching process involves several sophisticated steps:

  1. Snapshot Creation: When navigating away, the browser preserves the page's complete state, including the DOM tree, JavaScript heap, and event listeners.
  2. Memory Storage: The snapshot is stored in a dedicated memory cache, separate from the HTTP cache.
  3. Execution Pause: All JavaScript execution halts, including timers, intervals, and pending promises. No code runs while the page is in bfcache.
  4. Selective Restoration: When returning, the browser rehydrates the page from memory, restoring only what has changed since the snapshot.
// Detect bfcache restoration
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Page was restored from bfcache
    console.log('bfcache restore time:', performance.now())
  }
})

Optimizing Your Pages for bfcache

To maximize bfcache eligibility and ensure your pages benefit from this optimization, follow these best practices:

  1. Avoid unload event handlers:
    The presence of a window.onunload or window.addEventListener('unload', ...) handler will disqualify your page from bfcache in most browsers. Use pagehide instead, which is bfcache-friendly.

  2. Close connections gracefully:
    Properly close IndexedDB, WebSockets, and BroadcastChannels in the pagehide event. Open connections can prevent bfcache from activating.

    window.addEventListener('pagehide', () => {
      if (myWebSocket) myWebSocket.close()
      if (myBroadcastChannel) myBroadcastChannel.close()
    })
  3. Handle restoration logic:
    Use the pageshow event to detect when a page is restored from bfcache and refresh dynamic content if needed.

    window.addEventListener('pageshow', (event) => {
      if (event.persisted) {
        fetchLatestContent() // Refresh stale data
      }
    })
  4. Mind your HTTP headers:
    The presence of Cache-Control: no-store or certain other headers (like Pragma: no-cache) will disable bfcache for that page. Use caching headers judiciously.

  5. Avoid using certain APIs:
    Some APIs, like SharedWorker, may prevent bfcache. Check bfcache eligibility for more details.


Common Pitfalls and Solutions

  1. Dynamic content updates:
    If your page displays real-time or user-specific data, ensure you refresh it on restore from bfcache using the pageshow event.1

  2. Authentication state changes:
    If a user's session may expire or change while the page is in bfcache, check authentication state on restore and redirect or update UI as needed.

  3. Analytics tracking:
    Avoid double-counting pageviews. Use the pageshow event and check event.persisted to distinguish between normal loads and bfcache restores.

  4. Single-page applications (SPAs):
    Framework routers may need to handle bfcache restores explicitly. For example, in Vue Router:

    router.beforeResolve((to, from, next) => {
      if (window.performance?.navigation?.type === 2) {
        // Back/forward navigation
        handleBfcacheRestore()
      }
      next()
    })
  5. Third-party scripts:
    Some analytics or chat widgets may register unload handlers or keep open connections, making your page ineligible for bfcache. Audit third-party code if you notice bfcache is not working.


Debugging bfcache

  • Chrome DevTools:
    Open DevTools → Application → Back/forward cache to see if your page is eligible and what might be blocking it.
  • Firefox:
    Use about:networking#bfcache to inspect bfcache entries.
  • Testing:
    Use the pageshow event and log event.persisted to verify bfcache restores.

bfcache and Modern Frameworks

  • React, Vue, Angular:
    Most modern frameworks work with bfcache, but you must avoid global unload handlers and ensure cleanup logic is in pagehide.
  • Next.js, Nuxt, etc.:
    For SSR/SSG apps, ensure dynamic data is refreshed on restore. Use the pageshow event in your root component or layout.

Limitations and Browser Support

  • Memory constraints:
    Browsers may evict pages from bfcache if memory is low or too many pages are cached.
  • Cross-origin navigations:
    Navigating between different origins may not use bfcache.
  • Not all APIs are supported:
    Some APIs (e.g., Web Locks, SharedWorker) may block bfcache.

See web.dev bfcache guide for up-to-date compatibility and caveats.


Summary

bfcache is a powerful browser optimization that can dramatically improve the user experience for back/forward navigation. By understanding how it works and following best practices, you can ensure your site is eligible for bfcache and delivers lightning-fast navigation.

Key takeaways:

  • Avoid unload handlers; use pagehide instead.
  • Clean up open connections and resources.
  • Use the pageshow event to refresh dynamic content.
  • Audit third-party scripts for bfcache compatibility.
  • Test and debug with browser tools.

Further reading:

Footnotes

  1. JavaScript - bfcache/pageshow event - event.persisted always set to false? ↩