Monitoring bfcache blocking reasons

Limited availability

This feature is not Baseline because it does not work in some of the most widely-used browsers.

Experimental: This is an experimental technology
Check the Browser compatibility table carefully before using this in production.

The PerformanceNavigationTiming.notRestoredReasons property reports information on why the current document was blocked from using the bfcache on navigation. Developers can use this information to identify pages that need updates to make them bfcache-compatible, thereby improving site performance.

Back/forward cache (bfcache)

Modern browsers provide an optimization feature for history navigation called the back/forward cache (bfcache). This enables an instant loading experience when users go back to a page they have already visited. Pages can be blocked from entering the bfcache or get evicted while in the bfcache for different reasons, some required by a specification and some specific to browser implementations.

To enable monitoring bfcache blocking reasons, the PerformanceNavigationTiming class includes a notRestoredReasons property. This returns a NotRestoredReasons object containing related information on the top-level frame and all <iframe>s present in the document:

  • Reasons why bfcache usage was blocked.
  • Details such as frame id and name, to help identify <iframe>s in the HTML.

Note: Historically, the deprecated PerformanceNavigation.type property was used to monitor the bfcache, with developers testing for a type of "TYPE_BACK_FORWARD" to get an indication of the bfcache hit rate. This however did not provide any reasons for bfcache blocking, or any other data. The notRestoredReasons property should be used to monitor bfcache blocking, going forward.

Logging bfcache blocking reasons

Ongoing bfcache blocking data can be obtained using a PerformanceObserver, like this:

js
const observer = new PerformanceObserver((list) => {
  let perfEntries = list.getEntries();
  perfEntries.forEach((navEntry) => {
    console.log(navEntry.notRestoredReasons);
  });
});

observer.observe({ type: "navigation", buffered: true });

Alternatively, you can obtain historical bfcache blocking data using a suitable method such as Performance.getEntriesByType():

js
function returnNRR() {
  const navEntries = performance.getEntriesByType("navigation");
  for (let i = 0; i < navEntries.length; i++) {
    console.log(`Navigation entry ${i}`);
    let navEntry = navEntries[i];
    console.log(navEntry.notRestoredReasons);
  }
}

The code snippets shown above will log NotRestoredReasons objects to the console. These objects have the following structure, which represents the blocked state of the top-level frame:

js
{
  children: [],
  id: null,
  name: null,
  reasons: [
    { reason: "unload-listener" }
  ],
  src: "",
  url: "example.com",
}

The properties are as follows:

children Read only Experimental

An array of NotRestoredReasons objects, one for each child <iframe> embedded in the current document, which may contain reasons why the top-level frame was blocked relating to the child frames. Each object has the same structure as the parent object — this way, any number of levels of embedded <iframe>s can be represented inside the object recursively. If the frame has no children, the array will be empty; if the document is in a cross-origin <iframe>, children will return null.

id Read only Experimental

A string representing the id attribute value of the <iframe> the document is contained in (for example <iframe id="foo" src="...">). If the document is not in an <iframe> or the <iframe> has no id set, id will return null.

name Read only Experimental

A string representing the name attribute value of the <iframe> the document is contained in (for example <iframe name="bar" src="...">). If the document is not in an <iframe> or the <iframe> has no name set, name will return null.

reasons Read only Experimental

An array of NotRestoredReasonDetails objects, each representing a reason why the navigated page was blocked from using the bfcache. If the document is in a cross-origin <iframe>, reasons will return null, but the parent document may show a reason of "masked" if any <iframe>s blocked bfcache usage for the top-level frame. See Blocking reasons for more details on the reasons.

src Read only Experimental

A string representing the path to the source of the <iframe> the document is contained in (for example <iframe src="exampleframe.html">). If the document is not in an <iframe>, src will return null.

url Read only Experimental

A string representing the URL of the navigated page or <iframe>. If the document is in a cross-origin <iframe>, url will return null.

Reporting bfcache blocking in same-origin <iframe>s

When a page has same-origin <iframe>s embedded, the returned notRestoredReasons value will contain an array of objects inside the children property representing the blocking reasons related to each embedded frame.

For example:

js
{
  children: [
    {
      children: [],
      id: "iframe-id",
      name: "iframe-name",
      reasons: [],
      src: "./index.html",
      url: "https://www.example.com/iframe-examples.html"
    },
    {
      children: [],
      id: "iframe-id2",
      name: "iframe-name2",
      reasons: [
        { "reason": "unload-listener" }
      ],
      src: "./unload-examples.html",
      url: "https://www.example.com/unload-examples.html"
    },
  ],
  id: null,
  name: null,
  reasons: [],
  src: null,
  url:"https://www.example.com"
}

Reporting bfcache blocking in cross-origin <iframe>s

When a page has cross-origin frames embedded, the amount of information shared about them is limited to avoid leaking cross-origin information. Only information that the outer page already knows is included, and whether the cross-origin subtree caused bfcache blocking or not. No blocking reasons or information about lower levels of the subtree (even if some sub-levels are same-origin) are included.

For example:

js
{
  children: [
    {
      children: [],
      id: "iframe-id",
      name: "iframe-name",
      reasons: [],
      src: "https://www.example2.com/",
      url: null
    }
  ],
  id: null,
  name: null,
  reasons: [
        { "reason": "masked" }
  ],
  src: null,
  url:"https://www.example.com"
}

For all the cross-origin <iframe>s, no blocking reasons are reported; for the top-level frame a reason of "masked" is reported, to indicate that the reasons are being kept hidden for privacy purposes. Note that "masked" may also be used for hiding user agent-specific reasons; it doesn't always indicate an issue in an <iframe>.

Blocking reasons

There are many different reasons why blocking could occur. Although the reasons are standardized, developers should avoid depending on specific wording for reasons and be prepared to handle new reasons being added and deleted.

The values listed in the specification are:

"fetch"

While unloading, a fetch initiated by the current document (e.g. via fetch()) was canceled while ongoing. As a result, the page was not in a stable state that could be stored in the bfcache.

"lock"

While unloading, held locks and lock requests were terminated, so the page was not in a stable state that could be stored in the bfcache.

"masked"

The exact reason is hidden for privacy purposes. This value can mean one of the following:

  • The current document has children contained in a cross-origin <iframe>, and they prevented storage in the bfcache.
  • The current Document could not be stored in the bfcache for user agent-specific reasons.

The original navigation that created the current document errored, and storing the resulting error document in the bfcache was prevented.

"parser-aborted"

The current document never finished its initial HTML parsing, and storing the unfinished document in the bfcache was prevented.

"websocket"

While unloading, an open WebSocket connect was shut down, so the page was not in a stable state that could be stored in the bfcache.

User-agent specific blocking reasons

Additional blocking reasons that may be used by some browsers are also specified:

"audio-capture"

The Document requested audio capture permission by using Media Capture and Streams's getUserMedia() with audio.

"background-work"

The Document requested background work by calling SyncManager's register() method, PeriodicSyncManager's register() method, or BackgroundFetchManager's fetch() method.

"broadcastchannel-message"

While the page was stored in back/forward cache, a BroadcastChannel connection on the page received a message and message event was fired.

"idbversionchangeevent"

The Document had a pending IDBVersionChangeEvent while unloading.

"idledetector"

The Document had an active IdleDetector while unloading.

"keyboardlock"

While unloading, keyboard lock was still active because Keyboard's lock() method was called.

"mediastream"

A MediaStreamTrack was in the live state upon unloading.

"midi"

The Document requested a MIDI permission by calling navigator.requestMIDIAccess().

"modals"

User prompts were shown while unloading.

While unloading, loading was still ongoing, and so the Document was not in a state that could be stored in back/forward cache.

The navigation request was canceled by calling window.stop() and the page was not in a state to be stored in back/forward cache.

"non-trivial-browsing-context-group"

The browsing context group of this Document had more than one top-level browsing context.

"otpcredential"

The Document created an OTPCredential.

"outstanding-network-request"

While unloading, the Document had outstanding network requests and was not in a state that could be stored in back/forward cache.

"paymentrequest"

The Document had an active PaymentRequest while unloading.

"pictureinpicturewindow"

The Document had an active PictureInPictureWindow while unloading.

"plugins"

The Document contained plugins.

"request-method-not-get"

The Document was created from an HTTP request whose method was not GET.

"response-auth-required"

The Document was created from an HTTP response that required HTTP authentication.

"response-cache-control-no-store"

The Document was created from an HTTP response whose Cache-Control header included the "no-store" token.

"response-cache-control-no-cache"

The Document was created from an HTTP response whose Cache-Control header included the "no-cache" token.

"response-keep-alive"

The Document was created from an HTTP response that contained a Keep-Alive header.

"response-scheme-not-http-or-https"

The Document was created from a response whose URL's scheme was not an HTTP(S) scheme.

"response-status-not-ok"

The Document was created from an HTTP response whose status was not an ok status.

"rtc"

While unloading, a RTCPeerConnection or RTCDataChannel was shut down, so the page was not in a state that could be stored in the back/forward cache.

"sensors"

The Document requested sensor access.

"serviceworker-added"

The Document's service worker client started to be controlled by a service worker while the page was in back/forward cache.

"serviceworker-claimed"

The Document's service worker client's active service worker was claimed while the page was in back/forward cache.

"serviceworker-postmessage"

The Document's service worker client's active service worker received a message while the page was in back/forward cache.

"serviceworker-version-activated"

The Document's service worker client's active service worker's version was activated while the page was in back/forward cache.

"serviceworker-unregistered"

The Document's service worker client's active service worker's service worker registration was unregistered while the page was in back/forward cache.

"sharedworker"

This Document was in the owner set of a SharedWorkerGlobalScope.

"smartcardconnection"

The Document had an active SmartCardConnection while unloading.

"speechrecognition"

The Document had an active SpeechRecognition while unloading.

"storageaccess"

The Document requested storage access permission by using the Storage Access API.

"unload-listener"

The Document registered an event listener for the unload event.

"video-capture"

The Document requested video capture permission by using Media Capture and Streams's getUserMedia() with video.

"webhid"

The Document called the WebHID API's requestDevice() method.

"webshare"

The Document used the Web Share API's navigator.share() method.

"webtransport"

While unloading, an open WebTransport connection was shut down, so the page was not in a state that could be stored in the back/forward cache.

"webxrdevice"

The Document created a XRSystem.

Browser compatibility

BCD tables only load in the browser

See also

Note: This article is adapted from Back/forward cache notRestoredReasons API by Chris Mills and Barry Pollard, originally published on developer.chrome.com in 2023 under the Creative Commons Attribution 4.0 License.