Loading
Back/Forward Cache (bfcache)

Back/Forward Cache (bfcache)

Overview

Analyzes Back/Forward Cache (bfcache) (opens in a new tab) to determine if your page is eligible for instant back/forward navigations. When bfcache works, users get instant (0ms) page loads when using browser back/forward buttons, dramatically improving perceived performance.

Why this matters:

bfcache is one of the most impactful performance optimizations available - it can make navigation instantaneous. However, many pages are unknowingly ineligible due to common patterns like unload handlers, Cache-Control: no-store, or ongoing network requests. This snippet helps identify why your page isn't being cached and how to fix it.

bfcache Impact:

Navigation TypeLoad TimeUser Experience
Normal navigation1-3sWait, spinner
bfcache restoration0msInstant

How bfcache works:

Common blocking reasons:

Snippet

// Back/Forward Cache (bfcache) Analysis
// https://webperf-snippets.nucliweb.net
 
(() => {
  const results = {
    supported: 'PerformanceNavigationTiming' in window,
    wasRestored: false,
    eligibility: null,
    blockingReasons: [],
    recommendations: [],
  };
 
  // Check if current page was restored from bfcache
  const checkRestoration = () => {
    // Check via pageshow event
    window.addEventListener('pageshow', (event) => {
      if (event.persisted) {
        results.wasRestored = true;
        console.log(
          '%c⚡ Page restored from bfcache!',
          'color: #22c55e; font-weight: bold; font-size: 14px;'
        );
        console.log('   Navigation was instant (0ms)');
      }
    });
 
    // Check via Performance Navigation Timing
    if (results.supported) {
      const navEntry = performance.getEntriesByType('navigation')[0];
      if (navEntry && navEntry.type === 'back_forward') {
        // Check activation timing for bfcache restore
        if (navEntry.activationStart > 0) {
          results.wasRestored = true;
        }
      }
    }
  };
 
  // Test bfcache eligibility
  const testEligibility = () => {
    const issues = [];
    const recs = [];
 
    // 1. Check for unload handlers
    const hasUnload = window.onunload !== null || window.onbeforeunload !== null;
    if (hasUnload) {
      issues.push({
        reason: 'unload/beforeunload handler detected',
        severity: 'high',
        description: 'These handlers block bfcache',
      });
      recs.push('Remove unload/beforeunload handlers. Use pagehide or visibilitychange instead.');
    }
 
    // 2. Check for Cache-Control: no-store
    // Note: Can't check response headers from JS, but we can detect it indirectly
    const meta = document.querySelector('meta[http-equiv="Cache-Control"]');
    if (meta && meta.content.includes('no-store')) {
      issues.push({
        reason: 'Cache-Control: no-store in meta tag',
        severity: 'high',
        description: 'Prevents page from being cached',
      });
      recs.push('Remove Cache-Control: no-store or change to no-cache.');
    }
 
    // 3. Check for open IndexedDB connections
    if (window.indexedDB) {
      // Can't directly check open connections, but we can warn
      const hasIndexedDB = performance.getEntriesByType('resource').some(
        r => r.name.includes('indexedDB')
      );
      if (hasIndexedDB) {
        issues.push({
          reason: 'IndexedDB may be in use',
          severity: 'medium',
          description: 'Open IndexedDB transactions block bfcache',
        });
        recs.push('Close IndexedDB connections before page hide.');
      }
    }
 
    // 4. Check for broadcast channels
    // Can't directly detect, but can check if BroadcastChannel exists
    if (window.BroadcastChannel) {
      // Just a warning - we can't detect if actually in use
      issues.push({
        reason: 'BroadcastChannel API available (check if in use)',
        severity: 'low',
        description: 'Open BroadcastChannel connections may block bfcache',
      });
    }
 
    // 5. Check for embedded iframes with issues
    const iframes = document.querySelectorAll('iframe');
    if (iframes.length > 0) {
      issues.push({
        reason: `${iframes.length} iframe(s) detected`,
        severity: 'medium',
        description: 'Iframes with bfcache blockers will block parent page',
      });
      recs.push('Ensure iframes are also bfcache compatible.');
    }
 
    // 6. Check for Service Worker
    if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
      // Service workers are OK, but just noting it
      issues.push({
        reason: 'Service Worker active',
        severity: 'info',
        description: 'Service Workers with fetch handlers are generally OK, but check for ongoing operations',
      });
    }
 
    // 7. Check for open WebSocket/WebRTC
    // Can't directly detect, but we can check for common libraries
    if (window.WebSocket) {
      issues.push({
        reason: 'WebSocket API available (check if connections are open)',
        severity: 'medium',
        description: 'Open WebSocket connections block bfcache',
      });
      recs.push('Close WebSocket connections before page hide.');
    }
 
    // 8. Check for ongoing fetch/XHR
    // Check if there are active resource requests
    const resources = performance.getEntriesByType('resource');
    const recent = resources.filter(r => r.responseEnd === 0 || (performance.now() - r.responseEnd < 100));
    if (recent.length > 0) {
      issues.push({
        reason: `${recent.length} recent/ongoing network requests`,
        severity: 'low',
        description: 'Ongoing requests may prevent bfcache',
      });
      recs.push('Ensure requests complete or are aborted on page hide.');
    }
 
    results.blockingReasons = issues;
    results.recommendations = recs;
 
    // Determine eligibility
    const highSeverity = issues.filter(i => i.severity === 'high').length;
    const mediumSeverity = issues.filter(i => i.severity === 'medium').length;
 
    if (highSeverity > 0) {
      results.eligibility = 'blocked';
    } else if (mediumSeverity > 1) {
      results.eligibility = 'likely-blocked';
    } else if (issues.length > 0) {
      results.eligibility = 'potentially-eligible';
    } else {
      results.eligibility = 'likely-eligible';
    }
 
    return results.eligibility;
  };
 
  // Display results
  const displayResults = () => {
    const statusIcons = {
      'likely-eligible': '🟢',
      'potentially-eligible': '🟡',
      'likely-blocked': '🟠',
      'blocked': '🔴',
    };
 
    const statusColors = {
      'likely-eligible': '#22c55e',
      'potentially-eligible': '#f59e0b',
      'likely-blocked': '#fb923c',
      'blocked': '#ef4444',
    };
 
    const statusText = {
      'likely-eligible': 'Likely Eligible',
      'potentially-eligible': 'Potentially Eligible',
      'likely-blocked': 'Likely Blocked',
      'blocked': 'Blocked',
    };
 
    const icon = statusIcons[results.eligibility] || '⚪';
    const color = statusColors[results.eligibility] || '#6b7280';
    const text = statusText[results.eligibility] || 'Unknown';
 
    console.group(
      `%c${icon} bfcache Status: ${text}`,
      `color: ${color}; font-weight: bold; font-size: 14px;`
    );
 
    console.log('');
    console.log('%c📊 Status:', 'font-weight: bold;');
 
    if (results.wasRestored) {
      console.log('%c   ✅ This page WAS restored from bfcache', 'color: #22c55e;');
      console.log('      Navigation was instant!');
    } else {
      console.log('   ℹ️  This page was NOT restored from bfcache');
      console.log('      (Either first visit or bfcache was blocked on previous navigation)');
    }
 
    // Navigation timing comparison
    if (results.supported) {
      const navEntry = performance.getEntriesByType('navigation')[0];
      if (navEntry) {
        console.log('');
        console.log('%c🕐 Navigation Timing:', 'font-weight: bold;');
        console.log(`   Type: ${navEntry.type}`);
        console.log(`   Duration: ${Math.round(navEntry.duration)}ms`);
 
        if (navEntry.type === 'back_forward') {
          if (navEntry.duration < 10) {
            console.log('%c   ⚡ Fast back/forward (likely from bfcache)', 'color: #22c55e;');
          } else {
            console.log('%c   🐌 Slow back/forward (full reload)', 'color: #ef4444;');
          }
        }
      }
    }
 
    // Display issues
    if (results.blockingReasons.length > 0) {
      console.log('');
      console.log('%c🔍 Potential Issues:', 'font-weight: bold;');
 
      const issueTable = results.blockingReasons.map(issue => ({
        Severity: issue.severity.toUpperCase(),
        Issue: issue.reason,
        Impact: issue.description,
      }));
 
      console.table(issueTable);
    } else {
      console.log('');
      console.log('%c✅ No obvious bfcache blockers detected!', 'color: #22c55e; font-weight: bold;');
    }
 
    // Recommendations
    if (results.recommendations.length > 0) {
      console.log('');
      console.log('%c💡 Recommendations:', 'color: #3b82f6; font-weight: bold;');
      results.recommendations.forEach((rec, idx) => {
        console.log(`   ${idx + 1}. ${rec}`);
      });
    }
 
    // How to test
    console.log('');
    console.log('%c🧪 How to Test:', 'font-weight: bold;');
    console.log('%c   IMPORTANT: Run snippet BEFORE navigating away!', 'color: #f59e0b;');
    console.log('');
    console.log('   1. Run this snippet (you already did this ✓)');
    console.log('   2. Navigate to another page');
    console.log('   3. Click browser Back button');
    console.log('   4. Check console for restoration message');
    console.log('');
    console.log('   Or use Chrome DevTools → Application → Back/forward cache');
 
    console.groupEnd();
 
    return results;
  };
 
  // Check for NotRestoredReasons API (Chrome 123+)
  const checkNotRestoredReasons = () => {
    if (!('PerformanceNavigationTiming' in window)) {
      return null;
    }
 
    const navEntry = performance.getEntriesByType('navigation')[0];
    if (!navEntry || !navEntry.notRestoredReasons) {
      return null;
    }
 
    console.group('%c🔬 Not Restored Reasons (Chrome 123+)', 'font-weight: bold; color: #3b82f6;');
 
    const reasons = navEntry.notRestoredReasons;
 
    console.log('');
    console.log('%cPage-level information:', 'font-weight: bold;');
 
    if (reasons.blocked === true) {
      console.log('%c   ❌ Page was blocked from bfcache', 'color: #ef4444;');
    } else {
      console.log('%c   ✅ Page was not blocked', 'color: #22c55e;');
    }
 
    if (reasons.url) {
      console.log(`   URL: ${reasons.url}`);
    }
 
    if (reasons.id) {
      console.log(`   Frame ID: ${reasons.id}`);
    }
 
    if (reasons.name) {
      console.log(`   Frame name: ${reasons.name}`);
    }
 
    if (reasons.src) {
      console.log(`   Source: ${reasons.src}`);
    }
 
    // Parse NotRestoredReasonDetails array
    // Each entry is an object with { reason: string, source: string }
    // Common reasons: "WebSocket", "unload-listener", "IndexedDB", etc.
    // Sources: "JavaScript", "UserAgentOnly", etc.
    if (reasons.reasons && reasons.reasons.length > 0) {
      console.log('');
      console.log('%cBlocking reasons:', 'font-weight: bold; color: #ef4444;');
 
      reasons.reasons.forEach((reasonDetail, idx) => {
        // reasonDetail is a NotRestoredReasonDetails object
        const reasonName = reasonDetail.reason || 'Unknown reason';
        const reasonSource = reasonDetail.source || 'Unknown source';
 
        console.group(`   ${idx + 1}. ${reasonName}`);
 
        // Show detailed information
        console.log(`      Reason: ${reasonName}`);
        console.log(`      Source: ${reasonSource}`);
 
        // Add helpful context for common reasons
        const reasonExplanations = {
          'WebSocket': 'Open WebSocket connections prevent bfcache. Close them on pagehide event.',
          'unload-listener': 'unload event listeners block bfcache. Use pagehide or visibilitychange instead.',
          'response-cache-control-no-store': 'Cache-Control: no-store header prevents caching. Change to no-cache.',
          'IndexedDB': 'Open IndexedDB transactions block bfcache. Close connections on pagehide.',
          'BroadcastChannel': 'Open BroadcastChannel prevents bfcache. Close it on pagehide.',
          'dedicated-worker': 'Dedicated workers can block bfcache. Terminate them on pagehide.',
        };
 
        if (reasonExplanations[reasonName]) {
          console.log(`      💡 ${reasonExplanations[reasonName]}`);
        }
 
        console.groupEnd();
      });
    }
 
    // Check children (iframes)
    if (reasons.children && reasons.children.length > 0) {
      console.log('');
      console.log('%cEmbedded frames:', 'font-weight: bold;');
 
      reasons.children.forEach((child, idx) => {
        console.group(`   ${idx + 1}. ${child.url || child.src || 'iframe'}`);
 
        if (child.blocked) {
          console.log('%c      Status: BLOCKED', 'color: #ef4444;');
        } else {
          console.log('%c      Status: OK', 'color: #22c55e;');
        }
 
        if (child.id) {
          console.log(`      Frame ID: ${child.id}`);
        }
 
        if (child.name) {
          console.log(`      Frame name: ${child.name}`);
        }
 
        if (child.reasons && child.reasons.length > 0) {
          console.log('      Blocking reasons:');
          child.reasons.forEach(reasonDetail => {
            console.log(`        • ${reasonDetail.reason || 'Unknown'}`);
            if (reasonDetail.source) {
              console.log(`          Source: ${reasonDetail.source}`);
            }
          });
        }
 
        console.groupEnd();
      });
    }
 
    // Summary table for easier visualization
    if (reasons.reasons && reasons.reasons.length > 0) {
      console.log('');
      console.log('%c📋 Summary Table:', 'font-weight: bold;');
 
      const reasonsTable = reasons.reasons.map(r => ({
        Reason: r.reason || 'Unknown',
        Source: r.source || 'N/A',
      }));
 
      console.table(reasonsTable);
    }
 
    console.groupEnd();
    return reasons;
  };
 
  // Check if snippet was executed after a back/forward navigation
  const checkExecutionTiming = () => {
    const navEntry = performance.getEntriesByType('navigation')[0];
    if (navEntry && navEntry.type === 'back_forward') {
      console.log('');
      console.group('%c⚠️  Timing Warning', 'color: #f59e0b; font-weight: bold;');
      console.log('%cSnippet executed AFTER back/forward navigation.', 'color: #f59e0b;');
      console.log('');
      console.log('For complete analysis including bfcache restoration detection:');
      console.log('   1. Run this snippet FIRST');
      console.log('   2. Then navigate away');
      console.log('   3. Then click Back button');
      console.log('');
      console.log('Current analysis shows NotRestoredReasons from the navigation that just occurred.');
      console.groupEnd();
      console.log('');
    }
  };
 
  // Initialize
  checkRestoration();
  const eligibility = testEligibility();
 
  // Display after a short delay to ensure pageshow event fires
  setTimeout(() => {
    checkExecutionTiming();
    displayResults();
 
    // Check NotRestoredReasons API if available
    const notRestoredReasons = checkNotRestoredReasons();
    if (!notRestoredReasons) {
      console.log('');
      console.log('%cℹ️  For detailed reasons, use Chrome 123+ and navigate back to this page.', 'color: #6b7280;');
    }
  }, 100);
 
  // Expose function for manual check
  window.checkBfcache = () => {
    testEligibility();
    displayResults();
    checkNotRestoredReasons();
    return results;
  };
 
  console.log('%c🚀 bfcache Analysis Running...', 'font-weight: bold; font-size: 14px;');
  console.log('   Results will appear shortly.');
  console.log(
    '   Call %ccheckBfcache()%c anytime to re-run analysis.',
    'font-family: monospace; background: #f3f4f6; padding: 2px 4px;',
    ''
  );
})();

Understanding bfcache

What is bfcache:

The back/forward cache (bfcache) stores a complete snapshot of a page when you navigate away. When you press the browser back button, the page is restored instantly from memory instead of reloading.

Benefits:

  • 0ms navigation: Instant page restoration
  • Preserved scroll position: Users return to exact scroll position
  • Preserved form state: Form inputs maintain their values
  • Preserved JavaScript state: Variables, timers, everything intact

When bfcache works:

User flow with bfcache:
Page A → Page B → [Back] → Page A (instant, 0ms)

User flow without bfcache:
Page A → Page B → [Back] → Page A (full reload, 1-3s)

Common Blockers and Solutions

BlockerWhy it blocksSolution
unload eventCan't be reliably replayedUse pagehide or visibilitychange
beforeunloadPrevents navigation cachingRemove or use pagehide for cleanup
Cache-Control: no-storeTells browser not to cacheChange to no-cache or private
Open IndexedDB transactionState can't be serializedClose transactions on pagehide
WebSocket connectionCan't be suspendedClose on pagehide, reconnect on pageshow
Ongoing fetch/XHRIncomplete network stateAbort on pagehide
BroadcastChannelCan't be suspendedClose on pagehide

Fixing Common Issues

Issue 1: unload/beforeunload handlers

Bad:

window.addEventListener('unload', () => {
  saveData(); // Blocks bfcache
});
 
window.addEventListener('beforeunload', () => {
  return 'Are you sure?'; // Blocks bfcache
});

Good:

window.addEventListener('pagehide', (event) => {
  // event.persisted tells you if page goes to bfcache
  if (event.persisted) {
    // Page will be cached, do minimal cleanup
    console.log('Page going to bfcache');
  } else {
    // Page being discarded, do full cleanup
    saveData();
  }
});

Issue 2: Cache-Control: no-store

Bad:

<meta http-equiv="Cache-Control" content="no-store">

Good:

<!-- Remove the meta tag entirely, or use: -->
<meta http-equiv="Cache-Control" content="no-cache">

Issue 3: Open WebSocket

Bad:

const ws = new WebSocket('wss://example.com');
// Connection stays open, blocks bfcache

Good:

const ws = new WebSocket('wss://example.com');
 
window.addEventListener('pagehide', () => {
  ws.close(); // Close on navigation
});
 
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Reconnect after bfcache restore
    ws = new WebSocket('wss://example.com');
  }
});

Issue 4: IndexedDB

Bad:

const request = indexedDB.open('myDB');
request.onsuccess = () => {
  const db = request.result;
  const transaction = db.transaction(['store'], 'readwrite');
  // Transaction stays open, blocks bfcache
};

Good:

let db;
const request = indexedDB.open('myDB');
request.onsuccess = () => {
  db = request.result;
};
 
window.addEventListener('pagehide', () => {
  db?.close(); // Close connection
});

Testing bfcache

Method 1: Manual test (recommended)

⚠️ Important: Execution order matters!

Correct order:

  1. First: Open your page
  2. Second: Run this snippet (to register event listeners)
  3. Third: Navigate to another page (e.g., click a link)
  4. Fourth: Press browser Back button
  5. Fifth: Check console for results

Why this order? The snippet needs to be listening for the pageshow event with persisted: true to detect bfcache restoration. If you run the snippet after clicking Back, you'll miss the restoration event.

If you run the snippet after navigating back:

  • ⚠️ You'll see a timing warning
  • ❌ Won't detect if page was restored from bfcache
  • ✅ Will still show NotRestoredReasons from that navigation
  • ✅ Will still analyze current eligibility

Output when run in correct order:

⚡ Page restored from bfcache!
   Navigation was instant (0ms)

Method 2: Chrome DevTools

  1. Open DevTools → Application tab
  2. Click "Back/forward cache" in sidebar
  3. Click "Test back/forward cache"
  4. DevTools will navigate forward then back
  5. See detailed blocking reasons

Method 3: Chrome flags (for testing)

Visit chrome://flags/#back-forward-cache and enable experimental features for testing.

NotRestoredReasons API (Chrome 123+)

Chrome 123+ provides the detailed notRestoredReasons API that shows exactly why bfcache was blocked. This is the most accurate way to diagnose bfcache issues.

API structure:

const navEntry = performance.getEntriesByType('navigation')[0];
if (navEntry.notRestoredReasons) {
  const reasons = navEntry.notRestoredReasons;
 
  // Page-level information
  console.log('Blocked:', reasons.blocked);        // boolean
  console.log('URL:', reasons.url);                // page URL
  console.log('Frame ID:', reasons.id);            // frame identifier
  console.log('Name:', reasons.name);              // frame name (if any)
  console.log('Source:', reasons.src);             // frame source (if iframe)
 
  // Array of NotRestoredReasonDetails objects
  reasons.reasons.forEach(detail => {
    console.log('Reason:', detail.reason);         // blocking reason
    console.log('Source:', detail.source);         // where it came from
  });
 
  // Nested iframes
  reasons.children.forEach(child => {
    console.log('Child URL:', child.url);
    console.log('Child blocked:', child.blocked);
    console.log('Child reasons:', child.reasons);
  });
}

The snippet automatically parses and displays this information in a readable format with color-coding and tables.

Understanding NotRestoredReasonDetails

Each blocking reason is a NotRestoredReasonDetails object with these properties:

PropertyTypeDescription
reasonstringThe specific blocking reason (e.g., "WebSocket", "unload-listener")
sourcestringSource location: "UserAgentOnly" (browser), "JavaScript" (your code), etc.

Common reason values:

ReasonWhat it means
unload-listenerPage has unload event listener
response-cache-controlCache-Control: no-store header
WebSocketOpen WebSocket connection
IndexedDBOpen IndexedDB transaction
BroadcastChannelOpen BroadcastChannel
related-active-contentsRelated page (popup/iframe) blocks caching

Example output interpretation:

// Real example from joanleon.dev
{
  blocked: false,
  url: "https://joanleon.dev/",
  reasons: [
    {
      reason: "WebSocket",
      source: "JavaScript"
    }
  ]
}

This tells us:

  • ✅ Page was not blocked this time (blocked: false)
  • ❌ But a WebSocket connection was detected
  • 📍 It came from JavaScript code (not browser extension)
  • 🎯 Action: Close WebSocket on pagehide event to enable bfcache

Interpreting Your Results

Scenario 1: Fast back/forward (< 10ms)

Type: back_forward
Duration: 8ms
✅ Page restored from bfcache

Excellent! bfcache is working perfectly.

Scenario 2: Slow back/forward (> 100ms) with reasons

Type: back_forward
Duration: 405ms
🐌 Slow back/forward (full reload)

Blocking reasons:
- WebSocket connection detected
- Source: JavaScript

Action needed: Your page has blockers preventing bfcache. Fix the listed issues.

Scenario 3: Blocked = false, but slow navigation

blocked: false
Duration: 405ms
Reasons: [WebSocket]

⚠️ Important: Even though blocked: false, the page wasn't cached on the previous navigation because of WebSocket. Fix it and test again with a fresh navigation cycle.

Testing Workflow

To get accurate notRestoredReasons data:

  1. First navigation: Open page → Navigate away

    • At this point, browser decides if page goes to bfcache
    • If there are blockers, page is discarded
  2. Back navigation: Click back button

    • notRestoredReasons is populated based on previous navigation
    • Check if blocked: true and what the reasons were
  3. Fix issues: Close connections, remove handlers

  4. Test again: Repeat from step 1

    • The previous test's blockers are now stored in notRestoredReasons
    • Run the snippet after clicking back to see results

Chrome DevTools shortcut:

  • DevTools → Application → Back/forward cache
  • Click "Test back/forward cache" button
  • Instantly see detailed blocking reasons

Reading the Output

The snippet displays detailed information about NotRestoredReasonDetails objects:

Blocking reasons format:

Blocking reasons:
1. WebSocket
   Reason: WebSocket
   Source: JavaScript
   💡 Open WebSocket connections prevent bfcache. Close them on pagehide event.

📋 Summary Table:
┌─────────┬────────────────┬────────────┐
│ (index) │ Reason         │ Source     │
├─────────┼────────────────┼────────────┤
│ 0       │ 'WebSocket'    │'JavaScript'│
└─────────┴────────────────┴────────────┘

Each blocking reason shows:

  • Reason: The specific blocker (e.g., "WebSocket", "unload-listener")
  • Source: Where it comes from ("JavaScript", "UserAgentOnly")
  • Context: Helpful explanation and recommended fix
  • Table: Summary of all reasons for easy comparison

Browser Support

Browserbfcache SupportNotRestoredReasons API
Chrome 96+✅ (123+)
Edge 96+✅ (123+)
Safari 14+
Firefox 86+

All modern browsers support bfcache, but diagnostics vary.

Common Mistakes and Real-World Examples

❌ Mistake 1: Running Snippet After Navigation (Wrong Order)

Real example from joanleon.dev when executed incorrectly:

What happened:

  1. User visited https://joanleon.dev/ (opens in a new tab)
  2. Clicked on a blog post
  3. Clicked browser Back button ← Navigation already happened
  4. Then ran the snippet ← Too late!

Output received:

⚠️ Timing Warning
Snippet executed AFTER back/forward navigation.

For complete analysis including bfcache restoration detection:
   1. Run this snippet FIRST
   2. Then navigate away
   3. Then click Back button

🟡 bfcache Status: Potentially Eligible

📊 Status:
   ℹ️  This page was NOT restored from bfcache
   (Either first visit or bfcache was blocked on previous navigation)

🕐 Navigation Timing:
   Type: back_forward
   Duration: 405ms
   🐌 Slow back/forward (full reload)

🔍 Potential Issues:
   • LOW: BroadcastChannel API available
   • MEDIUM: WebSocket API available (check if connections are open)

🔬 Not Restored Reasons (Chrome 123+):
   Page-level information:
   ✅ Page was not blocked
   URL: https://joanleon.dev/

   Blocking reasons:
   1. WebSocket
      Reason: WebSocket
      Source: JavaScript
      💡 Open WebSocket connections prevent bfcache. Close them on pagehide event.

Why this output is incomplete:

  • ❌ Missed the pageshow event with persisted=true
  • ❌ Cannot confirm if page was actually restored from bfcache
  • ✅ Still shows NotRestoredReasons (helpful for diagnosis)
  • ✅ Still shows current eligibility analysis

Diagnosis from available data:

  1. Good news: No unload handlers, no Cache-Control issues
  2. Problem: WebSocket connection blocking bfcache
  3. 📊 Impact: 405ms reload vs. potential 0ms with bfcache
  4. 🎯 Solution needed: Close WebSocket on pagehide

✅ Example 2: Running Snippet in Correct Order

Correct workflow for joanleon.dev:

What to do:

  1. Visit https://joanleon.dev/ (opens in a new tab)
  2. First: Run the snippet ← Sets up event listeners
  3. Click on a blog post
  4. Click browser Back button ← Snippet captures everything

Output with listeners active (before fix):

🚀 bfcache Analysis Running...
   Listening for pageshow event...
   Ready to detect bfcache restoration.

[User navigates away and comes back...]

🟡 bfcache Status: Potentially Eligible

📊 Status:
   ℹ️  This page was NOT restored from bfcache
   (Snippet detected bfcache was blocked on this navigation)

🕐 Navigation Timing:
   Type: back_forward
   Duration: 405ms
   🐌 Slow back/forward (full reload)

🔍 Potential Issues:
   • MEDIUM: WebSocket API available (check if connections are open)

🔬 Not Restored Reasons (Chrome 123+):
   Blocking reasons:
   1. WebSocket
      Reason: WebSocket
      Source: JavaScript
      💡 Open WebSocket connections prevent bfcache. Close them on pagehide event.

Fix applied:

let ws = new WebSocket('wss://example.com/updates');
 
// Close connection when leaving page
window.addEventListener('pagehide', () => {
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.close();
  }
});
 
// Reconnect after bfcache restore
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Page restored from bfcache, reconnect
    ws = new WebSocket('wss://example.com/updates');
  }
});

Output after fix (with snippet running first):

🚀 bfcache Analysis Running...
   Listening for pageshow event...

[User navigates away and comes back...]

⚡ Page restored from bfcache!
   Navigation was instant (0ms)

🟢 bfcache Status: Likely Eligible

📊 Status:
   ✅ This page WAS restored from bfcache
   Navigation was instant!

🕐 Navigation Timing:
   Type: back_forward
   Duration: 3ms
   ⚡ Fast back/forward (likely from bfcache)

🔍 Potential Issues:
   • No significant blockers detected

🔬 Not Restored Reasons (Chrome 123+):
   Page-level information:
   ✅ Page was not blocked
   URL: https://joanleon.dev/

Impact: 405ms → 3ms = 99.3% faster back navigations

Key takeaway: Running the snippet in the correct order provides complete visibility into bfcache restoration, allowing you to confirm that fixes actually work.

Real-World Impact

Case studies:

  • Wikipedia: 40% faster back navigations with bfcache
  • E-commerce sites: 50% reduction in bounce rate from back button
  • News sites: 60% of back navigations became instant

Measuring impact:

Track navigation timing with performance.getEntriesByType('navigation'):

  • With bfcache: duration < 10ms, type === 'back_forward'
  • Without bfcache: duration > 1000ms, full reload

RUM Integration

// Track bfcache restoration in RUM
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Page restored from bfcache
    gtag('event', 'bfcache_restoration', {
      navigation_type: 'back_forward',
      duration: 0,
    });
  }
});
 
// Track bfcache eligibility
const navEntry = performance.getEntriesByType('navigation')[0];
if (navEntry && navEntry.notRestoredReasons) {
  const blocked = navEntry.notRestoredReasons.blocked;
  const reasons = navEntry.notRestoredReasons.reasons || [];
 
  gtag('event', 'bfcache_eligibility', {
    blocked: blocked,
    reasons: reasons.join(', '),
  });
}

Further Reading