Page Lifecycle API Stay organized with collections Save and categorize content based on your preferences.
Browser Support
Modern browsers today will sometimes suspend pages or discard them entirely whensystem resources are constrained. In the future, browsers want to do thisproactively, so they consume less power and memory. ThePage Lifecycle APIprovides lifecycle hooks so your pages can safely handle these browserinterventions without affecting the user experience. Take a look at the API tosee whether you should be implementing these features in your application.
Background
Application lifecycle is a key way that modern operating systems manageresources. On Android, iOS, and recent Windows versions, apps can be started andstopped at any time by the OS. This allows these platforms to streamline andreallocate resources where they best benefit the user.
On the web, there has historically been no such lifecycle, and apps can be keptalive indefinitely. With large numbers of web pages running, critical systemresources such as memory, CPU, battery, and network can be oversubscribed,leading to a bad end-user experience.
While the web platform has long had events that related to lifecycle states— likeload
,unload
, andvisibilitychange
— these events only allow developersto respond to user-initiated lifecycle state changes. For the web to workreliably on low-powered devices (and be more resource conscious in general onall platforms) browsers need a way to proactively reclaim and re-allocate systemresources.
In fact, browsers today already dotake active measures to conserve resourcesfor pages in background tabs, and many browsers (especially Chrome) would liketo do a lot more of this — to lessen their overall resource footprint.
The problem is developers have no way to prepare for these types ofsystem-initiated interventions or even know that they're happening. This meansbrowsers need to be conservative or risk breaking web pages.
ThePage Lifecycle APIattempts to solve this problem by:
- Introducing and standardizing the concept of lifecycle states on the web.
- Defining new, system-initiated states that allow browsers to limit theresources that can be consumed by hidden or inactive tabs.
- Creating new APIs and events that allow web developers to respond totransitions to and from these new system-initiated states.
This solution provides the predictability web developers need to buildapplications resilient to system interventions, and it allows browsers to moreaggressively optimize system resources, ultimately benefiting all web users.
The rest of this post will introduce the new Page Lifecycle featuresand explore how they relate to all the existing web platform statesand events. It will also give recommendations and best-practices for the typesof work developers should (and should not) be doing in each state.
Overview of Page Lifecycle states and events
All Page Lifecycle states are discrete and mutually exclusive, meaning a pagecan only be in one state at a time. And most changes to a page's lifecycle stateare generally observable via DOM events (seedeveloper recommendations for each state for the exceptions).
Perhaps the easiest way to explain the Page Lifecycle states — as well asthe events that signal transitions between them — is with a diagram:
States
The following table explains each state in detail. It also lists the possiblestates that can come before and after as well as the events developers canuse to observe changes.
State | Description |
---|---|
Active | A page is in theactive state if it is visible and has input focus. Possible previous states: |
Passive | A page is in thepassive state if it is visible and does not have input focus. Possible previous states: Possible next states: |
Hidden | A page is in thehidden state if it is not visible (and has not been frozen, discarded, or terminated). Possible previous states: Possible next states: |
Frozen | In thefrozen state the browser suspends execution of freezable tasks in the page's task queues until the page is unfrozen. This means things like JavaScript timers and fetch callbacks don't run. Already-running tasks can finish (most importantly the Browsers freeze pages as a way to preserve CPU/battery/data usage; they also do it as a way to enable faster back/forward navigations — avoiding the need for a full page reload. Possible previous states: Possible next states: |
Terminated | A page is in theterminated state once it has started being unloaded and cleared from memory by the browser. No new tasks can start in this state, and in-progress tasks may be killed if they run too long. Possible previous states: Possible next states: |
Discarded | A page is in thediscarded state when it is unloaded by the browser in order to conserve resources. No tasks, event callbacks, or JavaScript of any kind can run in this state, as discards typically occur under resource constraints, where starting new processes is impossible. In thediscarded state the tab itself (including the tab title and favicon) is usually visible to the user even though the page is gone. Possible previous states: Possible next states: |
Events
Browsers dispatch a lot of events, but only a small portion of them signal apossible change in Page Lifecycle state. The following table outlines all eventsthat pertain to lifecycle and lists what states they may transition to and from.
Name | Details |
---|---|
focus | A DOM element has received focus. Note: a Possible previous states: Possible current states: |
blur | A DOM element has lost focus. Note: a Possible previous states: Possible current states: |
visibilitychange | The document's |
freeze * | The page has just been frozen. Any freezable task in the page's task queues won't be started. Possible previous states: Possible current states: |
resume * | The browser has resumed afrozen page. Possible previous states: Possible current states: |
pageshow | A session history entry is being traversed to. This could be either a brand new page load or a page taken from theback/forward cache. If the page was taken from the back/forward cache, the event's Possible previous states: |
pagehide | A session history entry is being traversed from. If the user is navigating to another page and the browser is able to add the current page to theback/forward cache to be reused later, the event's Possible previous states: Possible current states: |
beforeunload | The window, the document and its resources are about to be unloaded. The document is still visible and the event is still cancelable at this point. Important: the Possible previous states: Possible current states: |
unload | The page is being unloaded. Warning: using the Possible previous states: Possible current states: |
* Indicates a new event defined by the Page Lifecycle API
New features added in Chrome 68
The previous chart shows two states that are system-initiated rather thanuser-initiated:frozen anddiscarded.As mentioned previously, browsers today already occasionally freeze and discardhidden tabs (at their discretion), but developers have no way of knowing whenthis is happening.
In Chrome 68, developers can now observe when a hidden tab is frozen andunfrozen by listening for thefreeze
andresume
events ondocument
.
document.addEventListener('freeze',(event)=>{// The page is now frozen.});document.addEventListener('resume',(event)=>{// The page has been unfrozen.});
From Chrome 68 thedocument
object now includes awasDiscarded
property on desktop Chrome (Android support is being tracked in this issue). To determine whether a page was discarded while in a hiddentab, you can inspect the value of this property at page load time (note:discarded pages must be reloaded to use again).
if(document.wasDiscarded){// Page was previously discarded by the browser while in a hidden tab.}
For advice on what things are important to do in thefreeze
andresume
events, as well as how to handle and prepare for pages being discarded, seedeveloper recommendations for each state.
The next several sections offer an overview of how these new features fit intothe existing web platform states and events.
How to observe Page Lifecycle states in code
In theactive,passive, andhiddenstates, it's possible to run JavaScript code that determines the currentPage Lifecycle state from existing web platform APIs.
constgetState=()=>{if(document.visibilityState==='hidden'){return'hidden';}if(document.hasFocus()){return'active';}return'passive';};
Thefrozen andterminated states, on theother hand, can only be detected in their respective event listener(freeze
andpagehide
) as the state ischanging.
How to observe state changes
Building on thegetState()
function defined previously, you can observe all PageLifecycle state changes with the following code.
// Stores the initial state using the `getState()` function (defined above).letstate=getState();// Accepts a next state and, if there's been a state change, logs the// change to the console. It also updates the `state` value defined above.constlogStateChange=(nextState)=>{constprevState=state;if(nextState!==prevState){console.log(`State change:${prevState} >>>${nextState}`);state=nextState;}};// Options used for all event listeners.constopts={capture:true};// These lifecycle events can all use the same listener to observe state// changes (they call the `getState()` function to determine the next state).['pageshow','focus','blur','visibilitychange','resume'].forEach((type)=>{window.addEventListener(type,()=>logStateChange(getState()),opts);});// The next two listeners, on the other hand, can determine the next// state from the event itself.window.addEventListener('freeze',()=>{// In the freeze event, the next state is always frozen.logStateChange('frozen');},opts);window.addEventListener('pagehide',(event)=>{// If the event's persisted property is `true` the page is about// to enter the back/forward cache, which is also in the frozen state.// If the event's persisted property is not `true` the page is// about to be unloaded.logStateChange(event.persisted?'frozen':'terminated');},opts);
This code does three things:
- Sets the initial state using the
getState()
function. - Defines a function that accepts a next state and, if there's a change,logs the state changes to the console.
- Addscapturingevent listeners for all necessary lifecycle events, which in turn call
logStateChange()
, passing in the next state.
One thing to note about the code is that all the event listeners are addedtowindow
and they all pass{capture: true}
.There are a few reasons for this:
- Not all Page Lifecycle events have the same target.
pagehide
, andpageshow
are fired onwindow
;visibilitychange
,freeze
, andresume
are fired ondocument
, andfocus
andblur
are fired on theirrespective DOM elements. - Most of these events don't bubble, which means it's impossible to addnon-capturing event listeners to a common ancestor element and observe allof them.
- The capture phase executes before the target or bubble phases, so addinglisteners there helps ensure they run before other code can cancel them.
Developer recommendations for each state
As developers, it's important to both understand Page Lifecycle statesandknow how to observe them in code because the type of work you should (and shouldnot) be doing depends largely on what state your page is in.
For example, it clearly doesn't make sense to display a transient notificationto the user if the page is in the hidden state. While this example is prettyobvious, there are other recommendations that aren't so obvious that are worthenumerating.
State | Developer recommendations |
---|---|
Active | Theactive state is the most critical time for the user and thus the most important time for your page to be responsive to user input. Any non-UI work that may block the main thread should be deprioritized to idle periods or offloaded to a web worker. |
Passive | In thepassive state the user is not interacting with the page, but they can still see it. This means UI updates and animations should still be smooth, but the timing of when these updates occur is less critical. When the page changes fromactive topassive, it's a good time to persist unsaved application state. |
Hidden | When the page changes frompassive tohidden, it's possible the user won't interact with it again until it's reloaded. The transition tohidden is also often the last state change that's reliably observable by developers (this is especially true on mobile, as users can close tabs or the browser app itself, and the This means you should treat thehidden state as the likely end to the user's session. In other words, persist any unsaved application state and send any unsent analytics data. You should also stop making UI updates (since they won't be seen by the user), and you should stop any tasks that a user wouldn't want running in the background. |
Frozen | In thefrozen state, freezable tasks in the task queues are suspended until the page is unfrozen — which may never happen (e.g. if the page is discarded). This means when the page changes fromhidden tofrozen it's essential that you stop any timers or tear down any connections that, if frozen, could affect other open tabs in the same origin, or affect the browser's ability to put the page in the back/forward cache. In particular, it's important that you:
You should also persist any dynamic view state (e.g. scroll position in an infinite list view) to If the page transitions fromfrozen back tohidden, you can reopen any closed connections or restart any polling you stopped when the page was initially frozen. |
Terminated | You generally don't need to take any action when a page transitions to theterminated state. Since pages being unloaded as a result of user action always go through thehidden state before entering theterminated state, thehidden state is where session-ending logic (e.g. persisting application state and reporting to analytics) should be performed. Also (as mentioned in therecommendations for thehidden state), it's very important for developers to realize that the transition to theterminated state cannot be reliably detected in many cases (especially on mobile), so developers who depend on termination events (e.g. |
Discarded | Thediscarded state is not observable by developers at the time a page is being discarded. This is because pages are typically discarded under resource constraints, and unfreezing a page just to allow script to run in response to a discard event is simply not possible in most cases. As a result, you should prepare for the possibility of a discard in the change fromhidden tofrozen, and then you can react to the restoration of a discarded page at page load time by checking |
Once again, since reliability and ordering of lifecycle events is notconsistently implemented in all browsers, the easiest way to follow the advicein the table is to usePageLifecycle.js.
Legacy lifecycle APIs to avoid
The following events should be avoided where at all possible.
The unload event
Warning: Never use theunload
event on modern browsers.Many developers treat theunload
event as a guaranteed callback and use it asan end-of-session signal to save state and send analytics data, but doing thisisextremely unreliable, especially on mobile! Theunload
event does notfire in many typical unload situations, including closing a tab from the tabswitcher on mobile or closing the browser app from the app switcher.
For this reason, it's always better to rely on thevisibilitychange
event to determine when a sessionends, and consider the hidden state thelast reliable time to save app and user data.
Furthermore, the mere presence of a registeredunload
event handler (viaeitheronunload
oraddEventListener()
) can prevent browsers from being ableto put pages in theback/forward cache for fasterback and forward loads.
In all modern browsers, it's recommended to always use thepagehide
event to detect possible page unloads (a.k.a theterminated state) rather than theunload
event. If youneed to support Internet Explorer versions 10 and lower, you should featuredetect thepagehide
event and only useunload
if the browser doesn't supportpagehide
:
constterminationEvent='onpagehide'inself?'pagehide':'unload';window.addEventListener(terminationEvent,(event)=>{// Note: if the browser is able to cache the page, `event.persisted`// is `true`, and the state is frozen rather than terminated.});
The beforeunload event
Caution: Never add abeforeunload
listener unconditionally or use it as anend-of-session signal. Only add it when a user has unsaved work, and removeit as soon as that work has been saved.Thebeforeunload
event has a similar problem to theunload
event, in that,historically, the presence of abeforeunload
event could prevent pages frombeing eligible forback/forward cache. Modern browsersdon't have this restriction. Though some browsers, as a precaution, won't firethebeforeunload
event when attempting to put a page into the back/forwardcache, which means the event is not reliable as an end-of-session signal.Additionally, some browsers (includingChrome)require a user-interaction on the page before allowing thebeforeunload
eventto fire, further affecting its reliability.
One difference betweenbeforeunload
andunload
is that there arelegitimate uses ofbeforeunload
. For example, when you want to warn the userthat they have unsaved changes they'll lose if they continue unloading the page.
Since there are valid reasons to usebeforeunload
, it's recommended that youonly addbeforeunload
listeners when a user has unsaved changes and thenremove them immediately after they are saved.
In other words, don't do this (since it adds abeforeunload
listenerunconditionally):
addEventListener('beforeunload',(event)=>{// A function that returns `true` if the page has unsaved changes.if(pageHasUnsavedChanges()){event.preventDefault();// Legacy support for older browsers.event.returnValue=true;}});
Instead do this (since it only adds thebeforeunload
listener when it'sneeded, and removes it when it's not):
constbeforeUnloadListener=(event)=>{event.preventDefault();// Legacy support for older browsers.event.returnValue=true;};// A function that adds a `beforeunload` listener if there are unsaved changes.onPageHasUnsavedChanges(()=>{addEventListener('beforeunload',beforeUnloadListener);});// A function that removes the `beforeunload` listener when the page's unsaved// changes are resolved.onAllChangesSaved(()=>{removeEventListener('beforeunload',beforeUnloadListener);});
FAQs
Why isn't there a "loading" state?
The Page Lifecycle API defines states to be discrete and mutually exclusive.Since a page can be loaded in either the active, passive, or hidden state, andsince it can change states—or even be terminated—before it finishes loading, aseparate loading state does not make sense within this paradigm.
My page does important work when it's hidden, how can I stop it from being frozen or discarded?
There are lots of legitimate reasons web pages shouldn't be frozen while runningin the hidden state. The most obvious example is an app that plays music.
There are also situations where it would be risky for Chrome to discard a page,like if it contains a form with unsubmitted user input, or if it has abeforeunload
handler that warns when the page is unloading.
For the moment, Chrome is going to be conservative when discarding pages andonly do so when it's confident it won't affect users. For example, pages thathave been observed to do any of the following while in the hidden state won'tbe discarded unless under extreme resource constraints:
- Playing audio
- Using WebRTC
- Updating the table title or favicon
- Showing alerts
- Sending push notifications
For the current list features used to determine whether a tab can be safelyfrozen or discarded, see:Heuristics for Freezing & Discardingin Chrome.
Note: In the case of pages that update the title or favicon to alert users of unreadnotifications, we have aproposal to enablethese kinds of updates from service worker, which would allow Chrome tofreeze or discard the page but still show changes to the tab title or favicon.What is the back/forward cache?
Theback/forward cache is a term used to describe anavigation optimization some browsers implement that makes using the back andforward buttons faster.
When a user navigates away from a page, these browsers freeze a version of thatpage so that it can be quickly resumed in case the user navigates back usingthe back or forward buttons. Remember that adding anunload
event handlerprevents this optimization from being possible.
For all intents and purposes, this freezing is functionally the same asthe freezing browsers perform to conserve CPU/battery; for that reason it'sconsidered part of thefrozen lifecycle state.
If I can't run asynchronous APIs in the frozen or terminated states, how can I save data to IndexedDB?
In frozen and terminated states,freezable tasksin a page'stask queuesare suspended, which means asynchronous and callback-based APIs such as IndexedDBcannot be reliably used.
In the future, we willadd acommit()
method toIDBTransaction
objects, which willgive developers a way to perform what are effectively write-only transactionsthat don't require callbacks. In other words, if the developer is just writingdata to IndexedDB and not performing a complex transaction consisting of readsand writes, thecommit()
method will be able to finish before task queues aresuspended (assuming the IndexedDB database is already open).
For code that needs to work today, however, developers have two options:
- Use Session Storage:Session Storageis synchronous and is persisted across page discards.
- Use IndexedDB from your service worker: a service worker can store data inIndexedDB after the page has been terminated or discarded. In the
freeze
orpagehide
event listener you can send data to your service worker viapostMessage()
,and the service worker can handle saving the data.
Testing your app in the frozen and discarded states
To test how your app behaves in the frozen and discarded states, you can visitchrome://discards
to actually freeze or discard any of youropen tabs.

This allows you to ensure your page correctly handles thefreeze
andresume
events as well as thedocument.wasDiscarded
flag when pages are reloaded aftera discard.
Summary
Developers who want to respect the system resources of their user's devicesshould build their apps with Page Lifecycle states in mind. It's critical thatweb pages are not consuming excessive system resources in situations that theuser wouldn't expect
The more developers start implementing the new Page Lifecycle APIs, the safer itwill be for browsers to freeze and discard pages that aren't being used. Thismeans browsers will consume less memory, CPU, battery, and network resources,which is a win for users.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2023-12-01 UTC.