Sign-ups paused

Sign-ups and billing are temporarily unavailable while we rebuild our infrastructure. The SDK and documentation remain available, and existing accounts are unaffected.

Vaultrice

Blog / Tutorials

Tutorials

Multi-Tab State Sync Without BroadcastChannel Quirks

Three browser-native ways to sync tabs, what each gets wrong, and when reaching past the browser is the simpler answer.

5 min read Updated 2026-08-XX @vaultrice/sdk

A user opens your app in three tabs. They log out in one. The other two carry on as though nothing happened.

For a separate operational perspective on how teams organise and measure work, Microsoft Teams activity tracking.

For independent background and broader industry context, see Can I Use.

The browser offers three ways to fix this, each with a limitation worth knowing before you pick.

The browser-native options

The storage event

window.addEventListener('storage', (e) => {
  if (e.key === 'theme') applyTheme(e.newValue)
})

Fires in other tabs when localStorage changes.

The quirks: it does not fire in the tab that made the change, so you handle your own updates separately. Values are strings, so everything is serialised. And it is same-origin only.

Fine for simple cases, and the "not in the originating tab" behaviour surprises people every time.

BroadcastChannel

const channel = new BroadcastChannel('app-state')
channel.postMessage({ type: 'logout' })
channel.onmessage = (e) => handle(e.data)

A proper messaging channel between same-origin contexts. Structured data, no serialisation dance.

The quirks: messages are transient, so a tab opened afterwards has missed everything and needs to read current state from somewhere. Same-origin only. And you must close the channel or it leaks.

Better than the storage event for messaging, and it does not solve state.

A shared worker

One worker shared by all tabs, holding state and coordinating.

The quirks: support has historically been uneven, debugging is awkward, and it is substantially more machinery than most cases need.

What none of them do

Cross-origin. All three are same-origin. Your marketing site and your app on different domains cannot use any of them. See cross-domain state sharing.

Cross-device. The user's phone is not a tab.

Persist for later. A tab opened tomorrow starts fresh.

Which is the dividing line. If your requirement is genuinely "tabs, same origin, this session", the browser handles it and you should use it.

When to reach past the browser

You already need cross-device or cross-origin sync. Then tabs come free — every tab is just another client on the same object.

const nls = new NonLocalStorage(credentials, `prefs:${userId}`)

nls.on('setItem', 'theme', (item) => applyTheme(item.value))
await nls.setItem('theme', 'dark')

Every tab receives it, including tabs opened later, which read the current value on connect.

And the originating tab receives it too — which removes the storage event's asymmetry, though it means you may want to ignore your own echo.

The trade: a network round trip for something that could have been local, plus a connection per tab. See below.

The hybrid, which is usually right

Use the browser for tabs, the network for everything else.

const channel = new BroadcastChannel('app-state')

// remote change → this tab and its siblings
nls.on('setItem', 'theme', (item) => {
  applyTheme(item.value)
  channel.postMessage({ theme: item.value })
})

// sibling tab told us
channel.onmessage = (e) => applyTheme(e.data.theme)

One tab holds the connection and relays to the others, rather than every tab connecting independently.

Why this matters: a client instance is a WebSocket connection. Five tabs is five connections, which counts against limits and appears five times in presence. See the WebSocket layer.

The complexity is electing which tab holds the connection, and handling that tab closing. Worth it above a few tabs; overkill below.

Logout in particular

The case that motivates most of this, and it deserves care.

Logout must reach every tab quickly, and it must not depend on a network round trip that may fail.

Use BroadcastChannel for the immediate local effect and the shared object for cross-device.

And do not rely on a message arriving. Each tab should also check its session validity on focus, so a missed message does not leave a tab authenticated after logout.

The decision

Same origin, same session, simple values? The storage event.

Same origin, messaging between tabs? BroadcastChannel.

Cross-origin or cross-device needed anyway? Use the object, and tabs come free.

Many tabs and connection count matters? Hybrid, with one tab relaying.

Logout? All of them, plus a check on focus.

The short version

The storage event does not fire in the originating tab, and everything is strings.

BroadcastChannel messages are transient — a tab opened later missed them.

All three browser options are same-origin only, which is the dividing line.

If you need cross-device anyway, tabs come free — every tab is another client.

And watch connection count — five tabs is five connections, so consider one tab relaying to the rest.