Multi-tab State Sync

In a browser, each tab is an independent instance. When more than one tabs hitting the same web site ([protocol]://[host]:[port]), they don't share states stored in the memory. To keep the states in sync across multiple tabs, we can take advantage of one of the following technologies:

In the following sections, we will use these technologies to implement a set of use cases:

LocalStorage + storage event

Tabs visiting the same origin share the same localStorage object. A storage event of the Window interface fires when localStorage has been modified in the context of another document. We can rely on the storage event to "broadcast" changes to other tabs.

For instance, we designed a simple flow in this demo:

  1. State changes in one tab (sender), it sets an entry in the localStorage, whose key is STATE_UPDATED and value is the JSON serialized payload.
  2. Immediately, the sender tab removes the entry from the localStorage.
  3. Other tabs (receivers) will receive two storage events: one for setting the value and one for removing it from the localStorage. They will only care about the first one.
  4. Receiver tabs parse the serialized payload and update their state.

BroadcastChannel

BroadcastChannel allows communication between tabs and workers on the same origin. Tabs can set up a STATE_UPDATED channel sync communicate state changes.

Compared to the localStorage approach, broadcast channel is more straightforward and performant. The API is specifically designed for sending a broadcast message. The message can be any type of object. Both sender and receiver tabs can work with the object without serialization.

The demo works in the following ways:

  1. State changes in the sender tab, it broadcasts the updated state to the STATE_UPDATED channel.
  2. The receiver tabs receives the message and update their state.

Service worker

All tabs of the same origin shares the same instance of a service worker. In a environment where broadcast channel is not available, service worker can be used to post messages to other tabs.

To post a message from client to service worker:


  navigator.serviceWorker.controller.postMessage(message);
    

To post a message from service worker to clients:


  const clients = await self.clients.matchAll({
    type: 'window',
  });
  if (clients) {
    for (const client of clients) {
      client.postMessage(message);
    }
  }
    

It's worth noting that service worker may terminate after 30 seconds of inactivity. Posting a message would wake up the service worker. This process would add extra latency.

The demo works in the following ways:

  1. State changes in the sender tab, it posts a message with the updated state to the service worker.
  2. The service worker receives the message and proxy the message to other tabs.
  3. Receiver tabs receive the message from the service worker and update their state.