Blog #23: Multi-tab Syncing – When your application 'talks' between tabs
A technical decision on messaging in the browser for a Fintech application requiring instant balance synchronization.
Team size: 6 people. Deadline: 1 month for the Personal Finance Management module. The project is at a critical stage with thousands of transactions performed every day. We've encountered a major user psychology issue: They often open the "Wallet" tab and the "Transaction History" tab side-by-side.
The Problem: When User renames a wallet in Tab A but Tab B still shows the old name
This isn't just an aesthetic issue; it's the reliability of a finance app. If a User deletes a transaction in Tab B, returns to Tab A and still sees the old balance, they will panic and think the system has a data error.
Our problem was: How to make the open tabs "know" that other tabs have just changed data without spamming requests to the Server continuously?
Options Considered
We came up with two main directions:
Option 1: Use WebSockets (The Heavy Rain)
- Solution: Establish a single socket connection. When the Server receives a change, it "broadcasts" it to all open tabs for that user.
- Pros: Extremely professional, near-instant speed. Synchronizes changes from other devices (Mobile vs Web).
- Cons: Backend bandwidth skyrockets, managing sockets is complex (reconnect, heartbeat). For this small module, it's a "using a sledgehammer to crack a nut" solution.
Option 2: Use BroadcastChannel API (The Local Hero)
- Solution: A built-in browser API that allows messaging between tabs/windows of the same-origin.
- Pros: Runs entirely on the Client-side, consumes no Server resources. Extremely simple installation (only about 10 lines of code).
- Cons: Only synchronizes between tabs on the same browser/computer. Does not receive changes if switching to another device.
Final Decision and Analysis
At that time, I decided to choose Option 2.
// Example of extremely simple synchronization
const channel = new BroadcastChannel('app_sync_channel');
// When data changes in Tab A
const updateProfile = (data) => {
saveToDB(data);
channel.postMessage({ type: 'PROFILE_UPDATED', payload: data });
};
// In all tabs (including Tab B)
channel.onmessage = (event) => {
if (event.data.type === 'PROFILE_UPDATED') {
mutate('/api/profile'); // Trigger React Query re-fetch
showNotify('Data has been updated from another tab!');
}
};
Impact on Performance: Near zero. The browser handles this messaging very lightly, occupying no Main Thread or network bandwidth.
Impact on Maintainability: The codebase remains "clean." Developers only need to search for the BroadcastChannel keyword to find all sync logic. No need to manage dozens of messy reconnect-socket logics.
Impact on Team: This solution is absolutely "friendly" to Juniors. They only need to know how to send messages (postMessage) and receive messages (onmessage), exactly the same as working with normal DOM events.
Self-Reflection: Was it Over-engineering?
I wonder: Why not use the storage event of LocalStorage? In reality, the storage event only triggers when a value in LocalStorage actually changes. If you want to send a signal to "Please reload data" without wanting to save all that data into LocalStorage, BroadcastChannel is a much more elegant choice.
If I went back to that time, would I choose WebSockets? Not for the multi-device problem, but for local synchronization between tabs, BroadcastChannel was a "just enough" and extremely effective decision that I still believe in to this day.
Notes on the silent understanding between application windows.
Series • Part 23 of 50