Mastering Multi-tab communication - Part 2

👋 Introduction
In Part 1, we explored the basics of the BroadcastChannel API — how it helps different tabs in a browser communicate with each other and why it’s an essential tool. Now, in Part 2, we’ll dive deeper into how the postMessage()
method works and how to handle cross-tab messages safely and efficiently.
By the end of this article, you’ll have a clear understanding of:
How to use
postMessage()
for sending messagesBest practices for structuring your messages
How to avoid common pitfalls when working with multiple tabs
Let’s get started!
🛠️ The postMessage()
Method
The core feature of the BroadcastChannel API is the ability to send messages between different tabs using the postMessage()
method. It’s straightforward but powerful. Here's a quick refresher:
// Create a new BroadcastChannel
const channel = new BroadcastChannel('app_channel');
// Send a message to all other tabs that are listening to the same channel
channel.postMessage('Hello, other tabs!');
How It Works:
Sender: When you call
postMessage()
, the current tab sends a message to all other tabs that have opened aBroadcastChannel
with the same name ('app_channel'
in this case).Receiver: Each tab that has a listener set up on that channel will receive the message.
The message is sent immediately across all connected tabs that are actively listening. Here’s a basic structure for receiving messages:
// Listening for messages from other tabs
channel.onmessage = (event) => {
console.log('Received message:', event.data);
};
Now that we know the basic mechanics, let’s look at what you should consider when handling messages.
🔐 Things to Handle When Using postMessage()
While using postMessage()
seems simple, it’s important to be careful to avoid common issues that might arise when dealing with multiple tabs and complex web apps.
1. Message Structure: Always use a clear structure when sending messages between tabs.
Since postMessage()
can send any serializable data (like strings, numbers, arrays, or objects), it’s essential to have a consistent structure to interpret the messages correctly.
Example: Message Structure
If you’re building a notification system, you could send an object with a specific structure:
// Sending a structured message
channel.postMessage({
type: 'notification',
payload: {
title: 'New Message',
body: 'You have a new message from John!'
}
});
This way, all your tabs know exactly how to handle incoming messages. For example:
// Handling the structured message
channel.onmessage = (event) => {
const { type, payload } = event.data;
if (type === 'notification') {
console.log('New Notification:', payload.title, payload.body);
}
};
Tip: Always check the structure of the message and handle it appropriately. You can even set up a message validation function if needed!
2. Avoiding Duplicate Messages: Ensure that a tab doesn’t act on the same message multiple times.
Imagine that your app is sending a "new data received" message across tabs, and multiple tabs receive the same message — each trying to handle the same action (e.g., refreshing a UI). This can lead to issues like duplicate refreshes.
To avoid that, you could add a message ID to each broadcasted message to keep track of which messages have been handled.
Example:
const message = {
id: 'msg123', // Unique identifier for the message
type: 'update',
data: { some: 'important data' }
};
// Send the message
channel.postMessage(message);
Each tab should track the IDs of the messages they’ve already processed. When a new message arrives, they can simply ignore it if they've already seen it.
const handledMessages = new Set();
channel.onmessage = (event) => {
const { id, type, data } = event.data;
if (!handledMessages.has(id)) {
handledMessages.add(id);
// Handle the message
console.log('New Message:', data);
} else {
console.log('Duplicate message ignored:', id);
}
};
3. Error Handling and Message Validation: Always be ready for unexpected issues.
Since you're working with multiple tabs, there’s always a chance that messages may be malformed or the tabs may not be able to receive them due to timing issues. It’s important to validate the messages before processing them.
Here’s how you can handle errors:
channel.onmessage = (event) => {
try {
const { type, payload } = event.data;
if (!type || !payload) {
throw new Error('Invalid message structure');
}
// Process the valid message
console.log('Valid message:', payload);
} catch (error) {
console.error('Error processing message:', error);
}
};
🚧 Common Pitfalls to Avoid
While the BroadcastChannel API is powerful, there are some common pitfalls you need to watch out for when using postMessage()
.
1. Message Overload
Sending too many messages at once, especially large data, can overwhelm your tabs. Always make sure the messages you’re sending are as lightweight as possible.
2. Non-Unique Message Identifiers
If multiple messages have the same ID, or if the ID isn’t properly tracked, you could run into issues where messages are reprocessed or duplicated across tabs.
3. Handling Unreceived Messages
If a tab isn’t actively listening (for example, if the tab is in the background or closed), it won’t receive messages. You can address this by checking the status of a tab before sending critical messages or implementing retry logic.
4. Cross-Tab Synchronization
Timing can sometimes be tricky. If you send a message while another tab is processing an important event, ensure that tabs have synchronized states (like after a successful login or before logout) to prevent errors.
🔄 Summary
In Part 2, we’ve gone through the inner workings of postMessage()
, best practices for structuring your messages, and how to handle communication effectively and safely across tabs.
Here's a quick recap of what we covered:
Use a consistent message structure
Avoid duplicates with message IDs
Always validate incoming messages to avoid errors
Be mindful of timing and synchronization across tabs
In Part 3, we’ll bring everything together by building a master-slave tab architecture that allows one tab to take control of another — implementing real-world functionality using BroadcastChannel!
👉 Read Part 3: Full Implementation — Master-Slave Tab Architecture with BroadcastChannel
Subscribe to my newsletter
Read articles from Sridhar Murali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sridhar Murali
Sridhar Murali
Passionate Frontend Engineer with over 4.5 years of experience in frontend development and a strong previous experience in Quality Assurance of 4 years (2016-2020). Skilled in building responsive, high-quality applications using JavaScript frameworks like Ember.js and React.js, combining meticulous attention to detail from a QA background with a commitment to delivering seamless user experiences. Recognized for writing efficient, accessible code, strong debugging skills