Offload heavy computation from the main thread using Web Workers, SharedWorkers, and Comlink โ structured messaging, transferable objects, and off-main-thread architecture patterns
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Offload heavy computation from the main thread using Web Workers, SharedWorkers, and Comlink โ structured messaging, transferable objects, and off-main-thread architecture patterns
layer
domain
category
frontend
triggers
["web worker","main thread","off main thread","SharedWorker","Comlink","postMessage","UI is janky","blocking the main thread","heavy computation","worker thread"]
inputs
["Computation or task to offload","Framework in use (React, Vue, vanilla, Next.js)","Build tool (Vite, webpack, Turbopack)","Data transfer requirements (size, frequency)","Whether shared state across tabs is needed"]
outputs
["Worker implementation with typed messaging","Comlink wrapper for RPC-style worker calls","Build configuration for worker bundling","Transfer strategy for large data (Transferable, SharedArrayBuffer)","Error handling and graceful degradation pattern"]
The browser's main thread handles DOM rendering, event listeners, and JavaScript execution on a single thread. Any computation taking more than ~50ms blocks user interaction and causes visible jank. Web Workers run JavaScript on separate OS threads, keeping the UI responsive while performing heavy computation โ parsing, sorting, image processing, cryptography, data transformation, or WASM execution.
Key Concepts
Worker Types
Type
Scope
Use Case
Dedicated Worker
Single page
Heavy computation for one tab
SharedWorker
Multiple tabs/frames on same origin
Shared WebSocket, cross-tab sync
Service Worker
Entire origin (network proxy)
Offline support, push notifications
Communication Model
Main Thread โโ Worker Thread
postMessage(data) โ onmessage(event)
onmessage(event) โ postMessage(data)
Data is COPIED by default (structured clone algorithm).
Transferable objects can be MOVED (zero-copy) for ArrayBuffers.
SharedArrayBuffer allows TRUE shared memory (requires COOP/COEP headers).
What Workers Cannot Access
DOM (document, window.document)
window (workers get self / globalThis)
localStorage / sessionStorage
Synchronous XHR (only in workers, but avoid it)
What Workers Can Access
fetch, WebSocket, IndexedDB, Cache API
crypto.subtle, TextEncoder/Decoder
importScripts() (classic) or ES modules
setTimeout, setInterval, requestAnimationFrame (not in all workers)
WASM instantiation and execution
Implementation
Basic Dedicated Worker
// worker.ts
self.onmessage = () => {
{ numbers } = event.;
sorted = [...numbers].( a - b);
mean = sorted.( a + b, ) / sorted.;
median = sorted[.(sorted. / )];
self.({ sorted, mean, median });
};
// vite.config.tsexportdefaultdefineConfig({
worker: {
format: 'es', // Use ES modules in workersplugins: () => [], // Plugins applied inside workersrollupOptions: {
output: {
entryFileNames: 'assets/worker-[name]-[hash].js',
},
},
},
});
// Workers are auto-detected with `new Worker(new URL(...), { type: 'module' })`// or with the `?worker` suffix:importMyWorkerfrom'./my-worker?worker';
const worker = newMyWorker();
Best Practices
Measure before offloading. Use the Performance API or Chrome DevTools to confirm a task actually blocks the main thread for >50ms. Workers add message-passing overhead.
Batch messages. Instead of sending one message per item, batch data into chunks. The structured clone overhead is per-message, not per-byte.
Use Transferable objects for large ArrayBuffers. Structured cloning a 100MB buffer takes ~80ms; transferring it takes ~0ms.
Pool workers for repeated tasks. Creating a new Worker has startup cost (~50-100ms). Reuse workers and route tasks via message types.
Always handle errors. Workers fail silently without onerror. Add error handlers and consider a timeout mechanism for unresponsive workers.
Use Comlink for complex APIs. Once a worker has more than 2-3 message types, Comlink's RPC pattern drastically reduces boilerplate.
Common Pitfalls
Pitfall
Symptom
Fix
Copying large data via postMessage
High memory usage, slow transfer
Use Transferable objects or SharedArrayBuffer
Not terminating workers
Memory leaks, zombie threads
Call worker.terminate() on component unmount or page unload
Accessing DOM from worker
ReferenceError: document is not defined
Workers have no DOM access โ send results back to main thread for DOM updates
Module workers not supported in older browsers
Worker fails to load
Add type: 'module' and check Worker constructor support; fall back to classic workers
SharedArrayBuffer without COOP/COEP headers
SharedArrayBuffer is not defined
Set Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp
Race conditions in SharedWorker
Inconsistent state across tabs
Use structured message protocol with sequence IDs; avoid shared mutable state