Real-Time Cross-Tab Sync with SharedArrayBuffer and Atomics (No Server, No BroadcastChannel)

Most web devs sync browser tabs using localStorage or BroadcastChannel. But what if you need instant, high-frequency data sharing between tabs — like a multiplayer editor or live data dashboard? Turns out, you can use SharedArrayBuffer + Atomics to achieve true shared memory between tabs — like low-level multi-threading in JavaScript. It’s crazy fast and doesn't need a server. Let’s build a blazing-fast, zero-latency cross-tab bus using shared memory. Step 1: Enable Cross-Origin Isolation (Required) For security, SharedArrayBuffer is only available in cross-origin isolated contexts. You’ll need to serve your site with special headers: Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp If you're using Vite, add this to vite.config.ts: server: { headers: { "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp" } } Now SharedArrayBuffer is enabled. Step 2: Create Shared Memory Buffer Each tab will access the same memory buffer using postMessage with transferable ownership. const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1024); const sharedArray = new Int32Array(sharedBuffer); You can use this like a raw memory segment. But how do other tabs get access to the same buffer? Step 3: Share Memory Between Tabs via window.open Unfortunately, different tabs can’t share memory automatically — but you can pass the buffer using postMessage and the window.open() reference. Tab A (main window): const child = window.open("/child.html"); child.addEventListener("load", () => { child.postMessage(sharedBuffer, "*", [sharedBuffer]); }); Tab B (child window): window.addEventListener("message", (e) => { const shared = new Int32Array(e.data); startWatching(shared); }); You now have shared memory between tabs. Step 4: Use Atomics to Sync Writes Use Atomics to coordinate read/write access and notify changes: // Writer (in Tab A) Atomics.store(sharedArray, 0, Date.now()); // write a timestamp Atomics.notify(sharedArray, 0); // notify any listener // Reader (in Tab B) function startWatching(shared) { function waitLoop() { Atomics.wait(shared, 0, 0); // wait until value changes const timestamp = Atomics.load(shared, 0); console.log("New data received:", new Date(timestamp)); waitLoop(); } waitLoop(); } This loop blocks (in a worker or idle time) until a change is written. It's fast — and zero-polling. Step 5: Add a Messaging Protocol You can extend the buffer to hold structured messages — like IDs, types, or payloads. // Convention: // Index 0: change flag // Index 1: message type // Index 2..: payload // Writer Atomics.store(sharedArray, 1, 42); // message type Atomics.store(sharedArray, 2, 1234); // payload Atomics.store(sharedArray, 0, 1); // signal change Atomics.notify(sharedArray, 0); You now have a structured event bus shared between browser tabs — no server or DB required. ✅ Pros: ⚡ Blazing-fast memory sharing across tabs

Apr 30, 2025 - 03:05
 0
Real-Time Cross-Tab Sync with SharedArrayBuffer and Atomics (No Server, No BroadcastChannel)

Most web devs sync browser tabs using localStorage or BroadcastChannel. But what if you need instant, high-frequency data sharing between tabs — like a multiplayer editor or live data dashboard?

Turns out, you can use SharedArrayBuffer + Atomics to achieve true shared memory between tabs — like low-level multi-threading in JavaScript. It’s crazy fast and doesn't need a server.

Let’s build a blazing-fast, zero-latency cross-tab bus using shared memory.

Step 1: Enable Cross-Origin Isolation (Required)

For security, SharedArrayBuffer is only available in cross-origin isolated contexts. You’ll need to serve your site with special headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

If you're using Vite, add this to vite.config.ts:

server: {
  headers: {
    "Cross-Origin-Opener-Policy": "same-origin",
    "Cross-Origin-Embedder-Policy": "require-corp"
  }
}

Now SharedArrayBuffer is enabled.

Step 2: Create Shared Memory Buffer

Each tab will access the same memory buffer using postMessage with transferable ownership.

const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1024);
const sharedArray = new Int32Array(sharedBuffer);

You can use this like a raw memory segment. But how do other tabs get access to the same buffer?

Step 3: Share Memory Between Tabs via window.open

Unfortunately, different tabs can’t share memory automatically — but you can pass the buffer using postMessage and the window.open() reference.

Tab A (main window):

const child = window.open("/child.html");

child.addEventListener("load", () => {
  child.postMessage(sharedBuffer, "*", [sharedBuffer]);
});

Tab B (child window):

window.addEventListener("message", (e) => {
  const shared = new Int32Array(e.data);
  startWatching(shared);
});

You now have shared memory between tabs.

Step 4: Use Atomics to Sync Writes

Use Atomics to coordinate read/write access and notify changes:

// Writer (in Tab A)
Atomics.store(sharedArray, 0, Date.now()); // write a timestamp
Atomics.notify(sharedArray, 0); // notify any listener
// Reader (in Tab B)
function startWatching(shared) {
  function waitLoop() {
    Atomics.wait(shared, 0, 0); // wait until value changes
    const timestamp = Atomics.load(shared, 0);
    console.log("New data received:", new Date(timestamp));
    waitLoop();
  }
  waitLoop();
}

This loop blocks (in a worker or idle time) until a change is written. It's fast — and zero-polling.

Step 5: Add a Messaging Protocol

You can extend the buffer to hold structured messages — like IDs, types, or payloads.

// Convention:
// Index 0: change flag
// Index 1: message type
// Index 2..: payload

// Writer
Atomics.store(sharedArray, 1, 42); // message type
Atomics.store(sharedArray, 2, 1234); // payload
Atomics.store(sharedArray, 0, 1); // signal change
Atomics.notify(sharedArray, 0);

You now have a structured event bus shared between browser tabs — no server or DB required.

Pros:

  • ⚡ Blazing-fast memory sharing across tabs