Rxova
Skip to content

Namespaces for micro-frontends

A BroadcastChannel is global to the origin, so a bus name is an identity. Two independently deployed apps that both call createSharedStore('cart', …) — or both omit the name and land on the default — are not two carts. They are one cart, with two teams writing to it.

Nothing warns about this, and nothing can: from the library’s side it is indistinguishable from the case it exists to serve, two tabs sharing state.

import { createNamespace } from 'use-everywhere';
export const checkout = createNamespace('checkout');

Every name from that namespace is prefixed, so checkout.useSharedState('items', []) lives on the bus checkout:use-everywhere and can’t be reached by anything that didn’t ask for the checkout namespace by name.

Call it at module scope, once per app, and export it:

checkout/bus.ts
export const checkout = createNamespace('checkout');
// checkout/Cart.tsx
import { checkout } from './bus';
function Cart() {
const [items, setItems] = checkout.useSharedState('items', []);
const peers = checkout.usePeers();
// …
}

It carries the full surface, not a reduced one — useSharedState, usePeers, useClientId, useLeader, useIsLeader, useLeaderEffect, defineStore, defineChannel, getSharedStore, plus the core factories (createSharedStore, createChannel, createPresence, createLeader) for code outside React.

Options keep their meanings. A store or name you pass is a name within the namespace:

checkout.useSharedState('theme', 'dark', { store: 'settings' });
// bus "checkout:settings"

Three different words that all sound like “scope”, kept deliberately distinct:

Question it answersExample
NamespaceWhose bus is this?createNamespace('checkout')
Share scopeHow far does this value travel?{ scope: 'tabs' }
wire.scopeWhich engine does this wire belong to?'state', 'presence'

They compose without interacting:

checkout.useSharedState('draft', '', { scope: 'tab' });
// ^ the checkout app's bus ^ but never leaves this tab

It is not a security boundary. Everything here is same-origin, and a namespace is a string — anything on the page can construct the same one deliberately. It prevents collision, not access. For the cross-origin case, see the security model.

It does not isolate you from version skew. Two deploys of the same namespace still meet, which is what you want — and if they speak different wire protocols they partition loudly. See Version skew & the wire contract.

busName() gives the real name, which is what devtools, observeBus and getTransportKind want:

observeBus(checkout.busName('settings'), (event) => console.log(event));
// listens on "checkout:settings"