Fix Back/Forward Cache Issues for Better Best Practices Score

Enable bfcache to make back button navigation instant. Learn common blockers and how to fix them.
Harlan WiltonHarlan Wilton3 min read Published

When users hit the back button, bfcache makes it instant by restoring the page from memory. If your page prevents bfcache, users wait for a full reload instead.

What's Happening

The back/forward cache (bfcache) stores complete page snapshots in memory when users navigate away. When they hit back or forward, the browser instantly restores the snapshot—no network requests, no JavaScript re-execution, no layout recalculation. It's the fastest navigation possible.

But many things can block bfcache: unload event handlers, open WebSocket connections, certain HTTP headers, and more. When blocked, the back button triggers a full page reload, adding seconds to what should be instant.

Lighthouse tests bfcache by simulating a back/forward navigation and checking if the page restored from cache. If restoration fails, it reports the specific blocking reasons.

Diagnose

Chrome DevTools Application Tab

  1. Open DevTools and go to Application tab
  2. Find "Back/forward cache" in the sidebar
  3. Click "Test back/forward cache"
  4. Review the blocking reasons if the test fails

The panel categorizes blockers as:

  • Actionable: Issues in your code you can fix
  • Pending browser support: Browser limitations that may be resolved in future updates
  • Not actionable: Inherent limitations (e.g., page is currently open in another tab)

Lighthouse Audit

The "Page prevented back/forward cache restoration" audit shows:

  • Each blocking reason with a description
  • The frame URL where the blocker was detected
  • Whether the blocker is actionable

Fix

1. Remove unload Event Handlers

The unload event is the most common bfcache blocker. It's unreliable anyway—mobile browsers often don't fire it.

// Bad: blocks bfcache
window.addEventListener('unload', () => {
  sendAnalytics()
})

// Good: use pagehide with persisted check
window.addEventListener('pagehide', (event) => {
  if (!event.persisted) {
    // Page is being destroyed, not cached
    sendAnalytics()
  }
})

// Better: use visibilitychange for analytics
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon('/analytics', data)
  }
})

Also check for beforeunload handlers that aren't conditionally added:

// Bad: always blocks bfcache
window.addEventListener('beforeunload', handleBeforeUnload)

// Good: only add when user has unsaved changes
function enableUnsavedWarning() {
  window.addEventListener('beforeunload', handleBeforeUnload)
}

function disableUnsavedWarning() {
  window.removeEventListener('beforeunload', handleBeforeUnload)
}

2. Close Open Connections

Persistent connections block bfcache because they can't be frozen and restored.

WebSockets:

// Close WebSocket when page is hidden
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    socket.close()
  }
})

// Reconnect when page becomes visible
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && !socket.connected) {
    socket.connect()
  }
})

IndexedDB transactions:

// Ensure transactions complete before page hide
window.addEventListener('pagehide', () => {
  // Let any pending transactions commit
  db.close()
})

3. Avoid Blocking HTTP Headers

Certain response headers prevent bfcache:

Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate

Cache-Control: private, max-age=0, must-revalidate

The no-store directive tells browsers to never cache the response, which extends to bfcache. Use private instead if you want to prevent CDN caching but allow bfcache.

4. Handle pageshow for Restored Pages

When a page is restored from bfcache, some state may be stale:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Page was restored from bfcache
    // Refresh stale data
    refreshUserSession()
    updateTimestamps()
    reconnectWebSocket()
  }
})

5. Address Third-Party Blockers

Third-party scripts often add unload handlers. Check for:

  • Analytics scripts (older versions of Google Analytics)
  • Chat widgets
  • Ad scripts
  • Social media embeds
// Audit what's listening to unload
getEventListeners(window).unload
// Returns array of unload handlers with source info

Update third-party scripts or defer their loading until interaction:

<!-- Defer chat widget until user interacts -->
<script>
  document.addEventListener('click', () => {
    loadChatWidget()
  }, { once: true })
</script>

Verify the Fix

  1. Open DevTools Application tab
  2. Navigate to "Back/forward cache"
  3. Click "Test back/forward cache"
  4. Confirm all actionable blockers are resolved
  5. Run Lighthouse to verify the audit passes
  6. Manually test: navigate away, then hit back—page should restore instantly without network activity

Common Mistakes

  • Third-party analytics — Older analytics libraries add unload handlers. Update to latest versions that use visibilitychange and sendBeacon.
  • Browser extensions — Extensions can inject blocking code. Test in incognito mode with extensions disabled.
  • Service worker fetch handlers — Complex fetch handlers can block bfcache. Keep service worker logic simple.
  • Cross-origin iframes — Iframes from other origins have their own bfcache eligibility. A blocked iframe blocks the whole page.
  • Testing in DevTools with throttling — Network throttling can interfere with bfcache testing. Test without throttling.

Pending Browser Support Issues

Some bfcache blockers aren't actionable yet:

  • BroadcastChannel: Having an open BroadcastChannel blocks bfcache in some browsers
  • SharedWorker: Using SharedWorker currently prevents bfcache
  • Keepalive fetch: Pending fetch requests with keepalive block restoration

These may be addressed in future browser updates. Monitor the Chrome status page for changes.

Test Your Entire Site

Different pages have different third-party scripts and connection patterns. Test bfcache across your entire site to catch all blockers.

Scan Your Site with Unlighthouse