DEV Community

Cover image for How to Sync Two Browser Tabs with JavaScript
CertosinoLab
CertosinoLab

Posted on

How to Sync Two Browser Tabs with JavaScript

Have you ever opened the same web app in two browser tabs and noticed that they behave like two completely separate applications?

Sometimes that's fine.

But sometimes you want an action in one tab to be immediately reflected in the others.

You could use a server, WebSockets, polling, or even localStorage.

But for simple communication between browser tabs, JavaScript already provides a native API:

BroadcastChannel.

Let's see how it works by building a very small counter that stays synchronized across multiple tabs.

The HTML

We only need a counter and a button:

<h2 id="counter">0</h2>
<button id="increment">+1</button>
Enter fullscreen mode Exit fullscreen mode

Nothing special so far.

Each browser tab will have its own JavaScript state:

let count = 0;
Enter fullscreen mode Exit fullscreen mode

Without any synchronization, clicking the button in one tab would only update that tab.

Let's fix that.

Create a BroadcastChannel

First, create a channel:

const channel = new BroadcastChannel("counter");
Enter fullscreen mode Exit fullscreen mode

The string "counter" is simply the name of the channel.

Any tab from the same origin that creates a BroadcastChannel with the same name can communicate with the others.

Now let's implement our counter.

const channel = new BroadcastChannel("counter");

let count = 0;

const counter = document.querySelector("#counter");
const button = document.querySelector("#increment");

button.onclick = () => {
  count++;

  counter.textContent = count;

  channel.postMessage(count);
};
Enter fullscreen mode Exit fullscreen mode

Every time we click the button, we increase the counter and then send the new value through the channel:

channel.postMessage(count);
Enter fullscreen mode Exit fullscreen mode

But the other tabs still need to listen for that message.

Receive messages from other tabs

We can do that with the message event:

channel.onmessage = ({ data }) => {
  count = data;
  counter.textContent = count;
};
Enter fullscreen mode Exit fullscreen mode

And that's basically the whole implementation.

Here is the complete JavaScript:

const channel = new BroadcastChannel("counter");

let count = 0;

const counter = document.querySelector("#counter");
const button = document.querySelector("#increment");

button.onclick = () => {
  count++;

  counter.textContent = count;

  channel.postMessage(count);
};

channel.onmessage = ({ data }) => {
  count = data;
  counter.textContent = count;
};
Enter fullscreen mode Exit fullscreen mode

Now open the page in two browser tabs.

Click +1 in the first tab:

Tab 1 → 1
Tab 2 → 1
Enter fullscreen mode Exit fullscreen mode

Then click +1 in the second tab:

Tab 1 → 2
Tab 2 → 2
Enter fullscreen mode Exit fullscreen mode

The two tabs are now communicating directly.

No server.

No WebSocket.

No polling.

What is actually happening?

When this line runs:

channel.postMessage(count);
Enter fullscreen mode Exit fullscreen mode

the browser sends the value to the other contexts listening to the "counter" channel.

The other tab receives it here:

channel.onmessage = ({ data }) => {
  // data contains the received value
};
Enter fullscreen mode Exit fullscreen mode

One important detail is that a channel does not send the message back to the same BroadcastChannel object that posted it.

That's why we update the current tab ourselves:

counter.textContent = count;
Enter fullscreen mode Exit fullscreen mode

and use BroadcastChannel to update the other tabs.

You can send more than numbers

BroadcastChannel isn't limited to strings or numbers.

You can send objects too:

channel.postMessage({
  type: "counter-updated",
  value: count
});
Enter fullscreen mode Exit fullscreen mode

And receive them normally:

channel.onmessage = ({ data }) => {
  if (data.type === "counter-updated") {
    count = data.value;
    counter.textContent = count;
  }
};
Enter fullscreen mode Exit fullscreen mode

For real applications, this approach is often better because different message types can share the same channel.

Same origin only

There is one important restriction.

BroadcastChannel works between browsing contexts that belong to the same origin.

For example, two tabs opened on:

https://example.com
Enter fullscreen mode Exit fullscreen mode

can communicate with each other.

A page on another domain cannot join that channel.

This makes BroadcastChannel useful for communication between different instances of the same web application.

When is this useful?

A synchronized counter is obviously just a demo.

In a real application, the same technique can be used to:

  • refresh data after another tab modifies it
  • synchronize application state
  • notify tabs when cached data becomes stale
  • coordinate background operations
  • communicate between tabs, windows and Web Workers

For simple cross-tab communication, it can save you from building a much more complicated solution.

Closing the channel

When you no longer need the channel, you can close it:

channel.close();
Enter fullscreen mode Exit fullscreen mode

For channels that exist for the entire lifetime of a page, this may not be especially important.

But it's useful when channels are created dynamically.

Final thought

BroadcastChannel is one of those small Web APIs that is easy to overlook.

The entire idea comes down to two operations.

Send something:

channel.postMessage(data);
Enter fullscreen mode Exit fullscreen mode

Receive something:

channel.onmessage = ({ data }) => {
  // react to the message
};
Enter fullscreen mode Exit fullscreen mode

So the next time you need two tabs of the same web app to communicate, you might not need a server or WebSocket connection.

Sometimes the browser already gives you exactly what you need.

Top comments (0)