LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #23: Multi-tab Syncing – When your application 'talks' between tabs
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

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.

TP
Truong PhamSoftware Engineer
PublishedMay 20, 2024

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

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #24: Failed Optimistic Update – When efforts to 'smooth' UI turn into UX disasters
Blog #22: Click Race Condition – When User's click speed beats your App
18Blog #18: Laggy Animations – When the touch is no longer smooth19Blog #19: Chrome Performance Tab – Looking through a microscope at the system's pulse20Blog #20: The Mystery of LCP – Why do 'fast' websites still get rated low?21Blog #21: The Global State Nightmare – The decision to 'topple' the Redux monument22Blog #22: Click Race Condition – When User's click speed beats your App23Blog #23: Multi-tab Syncing – When your application 'talks' between tabsReading24Blog #24: Failed Optimistic Update – When efforts to 'smooth' UI turn into UX disasters25Blog #25: Context API Perf Hit – When the 'standard React' solution betrays you26Blog #26: Don't let 'Single Source of Truth' become blind dogma27Blog #27: useEffect is not the place for doing all synchronizing logic28Blog #28: The naivety of believing setState is an immediate assignment
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles