Shared Workers
This lesson explains Shared Workers with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 Shared Workers API Overview
The HTML5 Shared Workers API allows multiple browser contexts from the
same origin to share one background worker. Unlike a Dedicated Worker,
which belongs to one page, a Shared Worker can be used by multiple tabs,
windows, iframes, or scripts at the same time.
Shared Workers are useful for sharing state, coordinating messages,
managing a single WebSocket connection, caching data in memory, handling
background calculations, syncing same-origin tabs, and reducing duplicate
work across multiple open pages.
| Feature | Description |
| API Name | Shared Workers API |
| Main Constructor | new SharedWorker() |
| Worker Script Context | SharedWorkerGlobalScope |
| Communication Object | MessagePort |
| Main Event | connect |
| Best Use | Shared state, cross-tab coordination, single background process. |
Basic Shared Worker Page Example
// main.js
if ("SharedWorker" in window) {
const worker =
new SharedWorker("/shared-worker.js");
worker.port.start();
worker.port.postMessage(
{
type: "PING",
message: "Hello from page"
}
);
worker.port.onmessage = (event) => {
console.log(
"Message from shared worker:",
event.data
);
};
} else {
console.log("Shared Workers are not supported.");
}
A page connects to a Shared Worker using new SharedWorker().
Communication happens through worker.port.
Basic Shared Worker Script
// shared-worker.js
self.addEventListener("connect", (event) => {
const port =
event.ports[0];
port.start();
port.onmessage = (messageEvent) => {
if (messageEvent.data.type === "PING") {
port.postMessage(
{
type: "PONG",
message: "Hello from shared worker"
}
);
}
}
});
The Shared Worker receives a connect event each time a new
page, tab, iframe, or window connects to it.
How Shared Workers Work
| Step | Description | Example Syntax |
| Create Worker | Page creates or connects to a shared worker. | new SharedWorker("/shared-worker.js") |
| Open Port | Start communication through the worker port. | worker.port.start() |
| Send Message | Page sends data to the worker. | worker.port.postMessage(data) |
| Connect Event | Worker receives connection from each page. | self.addEventListener("connect", callback) |
| Store Port | Worker can keep each connected page port. | ports.push(port) |
| Broadcast Reply | Worker sends messages to one or more connected pages. | port.postMessage(message) |
Shared Worker Methods and Properties
| Method / Property | Description | Example Syntax |
new SharedWorker() | Creates or connects to a shared worker. | new SharedWorker("/shared-worker.js") |
worker.port | MessagePort used to communicate with the worker. | worker.port.postMessage(data) |
port.start() | Starts receiving messages on a MessagePort. | worker.port.start() |
port.postMessage() | Sends a message through the port. | port.postMessage(message) |
port.onmessage | Handles messages from the connected context. | port.onmessage = callback |
connect | Worker event fired when a context connects. | self.addEventListener("connect", callback) |
Broadcast Message to All Connected Tabs
// shared-worker.js
const ports =
[];
self.addEventListener("connect", (event) => {
const port =
event.ports[0];
ports.push(port);
port.start();
port.onmessage = (messageEvent) => {
ports.forEach((connectedPort) => {
connectedPort.postMessage(
{
type: "BROADCAST",
message: messageEvent.data.message
}
);
});
}
});
This example keeps a list of connected ports and sends a message to every
connected tab or window.
Shared State Across Tabs
// shared-worker.js
let counter = 0;
const ports = [];
function broadcastCounter() {
ports.forEach((port) => {
port.postMessage(
{
type: "COUNTER_UPDATED",
value: counter
}
);
});
}
self.addEventListener("connect", (event) => {
const port =
event.ports[0];
ports.push(port);
port.start();
port.postMessage(
{
type: "COUNTER_UPDATED",
value: counter
}
);
port.onmessage = (messageEvent) => {
if (messageEvent.data.type === "INCREMENT") {
counter += 1;
broadcastCounter();
}
}
});
// main.js
const worker =
new SharedWorker("/shared-worker.js");
worker.port.start();
worker.port.onmessage = (event) => {
if (event.data.type === "COUNTER_UPDATED") {
document.querySelector("#count").textContent =
event.data.value;
}
}
document
.querySelector("#increment")
.addEventListener("click", () => {
worker.port.postMessage(
{
type: "INCREMENT"
}
);
});
This pattern allows multiple tabs to share one in-memory counter while
keeping the UI in each tab synchronized.
Single WebSocket Connection Example
// shared-worker.js
const ports = [];
const socket =
new WebSocket("wss://example.com/live");
socket.addEventListener("message", (event) => {
ports.forEach((port) => {
port.postMessage(
{
type: "SOCKET_MESSAGE",
data: event.data
}
);
});
});
self.addEventListener("connect", (event) => {
const port =
event.ports[0];
ports.push(port);
port.start();
port.onmessage = (messageEvent) => {
if (messageEvent.data.type === "SEND_SOCKET") {
socket.send(
messageEvent.data.payload
);
}
}
});
A Shared Worker can maintain one WebSocket connection and share updates
across multiple tabs, reducing duplicate server connections.
Shared In-Memory Cache Example
// shared-worker.js
const cache =
new Map();
self.addEventListener("connect", (event) => {
const port =
event.ports[0];
port.start();
port.onmessage = async (messageEvent) => {
const request =
messageEvent.data;
if (request.type === "GET_PRODUCT") {
if (cache.has(request.id)) {
port.postMessage(
{
type: "PRODUCT_RESULT",
product: cache.get(request.id),
source: "cache"
}
);
return;
}
const response =
await fetch("/api/products/" + request.id);
const product =
await response.json();
cache.set(request.id, product);
port.postMessage(
{
type: "PRODUCT_RESULT",
product: product,
source: "network"
}
);
}
}
});
Shared Workers can cache API data in memory and reuse it across tabs
during the worker lifetime.
Shared Worker Error Handling
// main.js
const worker =
new SharedWorker("/shared-worker.js");
worker.onerror = (error) => {
console.error(
"Shared Worker error:",
error.message
);
}
worker.port.onmessageerror = (error) => {
console.error(
"Message could not be cloned:",
error
);
}
Handle worker errors and message serialization issues to make shared
background features more reliable.
Structured Message Contract Example
worker.port.postMessage(
{
type: "USER_STATUS_CHANGED",
version: 1,
payload: {
userId: 101,
status: "online"
}
}
);
Using a clear message structure with type,
version, and payload makes communication easier
to validate, document, and maintain.
Dedicated Worker vs Shared Worker
| Feature | Dedicated Worker | Shared Worker |
| Owned By | One page or script. | Multiple same-origin pages, tabs, or iframes. |
| Constructor | new Worker() | new SharedWorker() |
| Communication | Direct postMessage(). | Uses MessagePort. |
| Best Use | Heavy work for one page. | Shared state or shared connection across tabs. |
| Connection Event | Not needed. | connect event. |
Shared Worker vs Broadcast Channel
| Feature | Shared Worker | Broadcast Channel |
| Shared Background Logic | Yes. | No, only messaging. |
| Can Keep Shared State | Yes. | Each tab manages its own state. |
| Can Maintain One WebSocket | Yes. | No. |
| Simplicity | More complex. | Simpler for tab messaging. |
| Best Use | Shared compute, cache, or connection. | Simple same-origin tab synchronization. |
Shared Worker Limitations
- Shared Workers cannot directly access the DOM.
- They work only across same-origin browsing contexts.
- Browser support may vary, especially on some mobile browsers.
- They require message passing for all communication.
- Worker lifetime is controlled by the browser.
- They are not a replacement for persistent storage.
- They should not store critical data only in memory.
- Debugging can be less obvious than normal page scripts.
Feature Detection
if ("SharedWorker" in window) {
console.log("Shared Workers are supported.");
} else {
console.log("Use Broadcast Channel or local fallback.");
}
Use feature detection and provide fallbacks such as Broadcast Channel,
Local Storage events, Service Workers, or duplicated per-tab workers.
Common Shared Worker Use Cases
- Sharing one WebSocket connection across tabs.
- Synchronizing same-origin application state.
- Managing shared in-memory cache.
- Coordinating notifications between tabs.
- Running background calculations for multiple pages.
- Reducing duplicate API requests across tabs.
- Cross-tab activity tracking.
- Micro frontend coordination on the same origin.
Shared Worker Best Practices
- Use Shared Workers only when multiple contexts need shared logic.
- Use structured message contracts with
type and payload. - Validate all incoming messages before acting on them.
- Keep worker logic focused and lightweight.
- Provide fallbacks for unsupported browsers.
- Do not rely on memory-only state for critical data.
- Store important data in IndexedDB or on the server.
- Handle errors and message serialization failures.
Security and Privacy Considerations
- Shared Workers are same-origin, but messages should still be validated.
- Do not store secrets only in worker memory.
- Avoid sending sensitive data unless required.
- Use HTTPS for production applications.
- Validate message type, payload shape, and permissions.
- Clean user-specific state on logout.
- Be careful with shared WebSocket messages across multiple tabs.
- Do not expose internal debug information through worker messages.
Common Shared Worker Mistakes
- Using a Shared Worker when a Dedicated Worker is enough.
- Forgetting to call
port.start(). - Assuming Shared Workers can access the DOM.
- Not storing connected ports for broadcast scenarios.
- Keeping critical data only in memory.
- Not validating incoming message payloads.
- Ignoring browser compatibility issues.
- Creating complex shared state that is difficult to debug.
Key Takeaways
- Shared Workers allow multiple same-origin contexts to share one worker.
- Use
new SharedWorker() to connect to a shared worker script. - Communication happens through
worker.port and MessagePort. - The worker receives a
connect event for every connected page. - Shared Workers are useful for shared state, shared cache, and one WebSocket connection.
- Use feature detection and fallback strategies because browser support can vary.
Pro Tip
Use a Shared Worker when multiple tabs need one shared background process.
For simple tab-to-tab messages, Broadcast Channel is often easier. For
heavy work used by only one page, a Dedicated Worker is usually simpler.