Synchronizing Browser Tabs with the BroadcastChannel API

← All posts

Today, unexpectedly, I came across a web API called BroadcastChannel, and decided to explore it in a blog post. Let’s dive in.

API overview

In essence, the API boils down to creating channels, and then posting and listening to messages on them. See for yourself:

// Create the channel objectconst channel = new BroadcastChannel('my-channel');
// Set up a listenerchannel.onmessage = message => console.log('Received:', message.data);
// Post a message to all subscriberschannel.postMessage('Hello, world');

You can try out the above by opening two blank tabs in your browser (note: it is not supported in Safari at the time of writing) and pasting the code into the console. The first tab you pasted it into should log a message once the code is pasted into the second one.

As you can see the API is quite minimal, but it is also very powerful in its simplicity. All you need to do is to create a channel object with an appropriate channel name, and all your app’s tabs can communicate seamlessly.

An interactive demo

Try entering a color name, such as ”hotpink”, or ”blue”, into the input element below:

Note

The BroadcastChannel API does not work in Safari, or webkit-based browser engines, such as Chrome on iOS. Please try the demo in a supported browser.

As you can see, the background color around the input element changes.

Now try opening this page in a new tab or browser window, and interact with the input again. Did you notice what happens? The color is kept in sync in both tabs!

A minimal reimplementation of the above demo in React might look like this, with the relevant areas highlighted in green:

function App() {  const [color, setColor] = React.useState('');
  const channel = React.useMemo(() => new BroadcastChannel('color'), []);
  React.useEffect(() => {    channel.onmessage = message => setColor(message.data);    return channel.close;  }, []);
  function onChange(event) {    setColor(event.target.value);    channel.postMessage(event.target.value);  }
  return (    <div style={{ backgroundColor: color }}>      <p>Enter a color, for example, try "hotpink" or "blue".</p>      <input type="text" value={color} onChange={onChange} />    </div>  );}

There’s also a CodeSandbox example here that you can interact with.

So what?

This API is not just for building demos; it can be used to synchronize all sorts of state across tabs. I’ve yet to make use of it for anything else besides the demo itself, but I can think of a few use cases where it would fit well:

  • Passing data across all the user’s tabs, such as the client-side application state, without making use of localStorage, WebSockets or external requests.

  • Avoid creating a WebSocket connection or making API calls from more than one tab at a time, instead passing the data along locally when possible.

  • Synchronizing user session state, like logging in across multiple tabs or logging out in the same fashion.

While the above examples do not present new solutions to previously unsolveable problems, they might present better solutions. For example, the localStorage API is synchronous, and if used heavily it will affect your app’s performance. It’s also limited in that it can only store data as strings, among other things. Depending on the application, saving network bandwidth could be a benefit.

Further reading

← All posts

If you’ve enjoyed reading this post, you can follow me on Twitter for updates.