Teams reach for the wrong isolation boundary and end up either starving a widget of the DOM access it needs or shipping a “worker” that never relieves the main thread — because a Web Worker and a sandboxed iframe solve fundamentally different problems.
The two mechanisms sound interchangeable — both “run the third-party code somewhere else” and both talk back via postMessage — but they draw the boundary along different axes. One separates execution time (which thread runs the code); the other separates execution context (which origin, DOM, and event loop the code sees). Picking the wrong one is not a tuning mistake you fix later; it is an architectural mismatch that surfaces as either a ReferenceError: document is not defined or a main thread that is still blocked despite all your effort. This guide sits under offloading heavy scripts to Web Workers and exists to make that choice deterministic.
Triage: Which Symptom Are You Solving
Before comparing mechanisms, name the failure you actually observed. The right boundary follows directly from the symptom, not from a general preference for “more isolation”.
If the script blocks the main thread but touches no DOM, you have a scheduling problem — a Web Worker fits. If it renders UI, reads cookies, or is untrusted code you must contain, you have a boundary problem — a sandboxed iframe fits. If it blocks the thread and insists on synchronous DOM access (most analytics and tag-manager SDKs), neither pure option works and you need Partytown’s DOM-proxying worker.
Root Cause: Two Boundaries That Are Not the Same Boundary
The confusion is structural. “Isolation” is an overloaded word, and the two APIs isolate along orthogonal axes.
A Web Worker isolates the thread, not the security surface
A Worker is a second JavaScript thread with its own event loop and its own DedicatedWorkerGlobalScope. It has no DOM access at all — no document, no window, no localStorage. It communicates with the page exclusively through postMessage, and every payload crosses the boundary via the structured clone algorithm (deep copy of plain data; functions, DOM nodes, and class instances with methods do not survive). ArrayBuffer, MessagePort, and ImageBitmap can instead be transferred with zero-copy semantics.
Crucially, a worker runs at the same origin as the page. It shares no memory object graph, but it is not a security sandbox: a worker can fetch() first-party endpoints with the user’s cookies, read same-origin resources, and reach any URL your connect-src allows. Its value is purely that long tasks inside it cannot block requestAnimationFrame, input handlers, or paint on the main thread. Use it to relieve the main thread; do not use it to contain untrusted code.
// Same-origin module worker. Relieves the main thread; NOT a security boundary.
const worker = new Worker(new URL('./telemetry.worker.js', import.meta.url), {
type: 'module',
});
// Structured clone: only plain data crosses. A DOM node here would throw DataCloneError.
worker.postMessage({ type: 'BATCH', events });
A sandboxed iframe isolates the context, and can be a real security boundary
A sandboxed <iframe> is a separate browsing context: its own document, its own window, its own event loop, and — when you serve it from a different origin or use sandbox without allow-same-origin — its own origin, which the same-origin policy then enforces. This is a genuine security boundary. The sandbox attribute drops privileges by default and you re-grant only what is needed (allow-scripts allow-forms), while omitting allow-same-origin forces the frame into an opaque origin that cannot read the parent’s cookies, storage, or DOM. Communication is again postMessage, but here you must validate event.origin on receipt — the detailed contract lives in cross-domain communication via postMessage, and the full privilege model in building secure iframes for third-party widgets.
The main-thread nuance is where most teams get it wrong. An iframe has its own event loop, but that does not automatically mean its work runs on a different thread. Under Chromium’s Site Isolation (and Firefox’s Fission), a cross-origin iframe is placed in a separate renderer process, so its scripting genuinely runs off the parent’s main thread. A same-origin or srcdoc iframe, however, typically shares the parent’s renderer process and its heavy synchronous work still competes for the same main thread — you get context and layout isolation, but little to no thread relief.
<!-- Cross-origin + no allow-same-origin: opaque origin, likely its own process. -->
<iframe
src="https://widget-sandbox.example.com/embed"
sandbox="allow-scripts allow-forms"
referrerpolicy="no-referrer"
loading="lazy"
></iframe>
Partytown: a worker that fakes the DOM
Real-world tags — Google Tag Manager, analytics pixels, A/B SDKs — assume synchronous access to document, window, and dataLayer. They cannot run unmodified in a bare worker. Partytown bridges this: it runs the third-party script inside a Web Worker but exposes a synchronous DOM facade to it, proxying every document/window access back to the main thread (historically via a synchronous XMLHttpRequest intercepted by a service worker). The third-party code believes it has the DOM; the main thread is relieved of its execution. The cost is added proxy latency per DOM operation and a service-worker/Atomics setup, so it suits fire-and-forget telemetry far better than latency-sensitive interactive widgets.
Resolution: The Decision Matrix
Read this top to bottom against the symptom you named in triage. Match the row whose best-fit use case describes your script.
| Mechanism | Isolation type | DOM access | Main-thread relief | Comms mechanism | Best-fit third-party use case | Key limitation |
|---|---|---|---|---|---|---|
| Web Worker | Thread (same origin) | None | Yes — full, own event loop | postMessage + structured clone; transferables for zero-copy |
Compute-heavy, DOM-free work: telemetry batching, hashing, compression, network delivery | No DOM and no security boundary — cannot run typical vendor SDKs unmodified |
| Sandboxed iframe | Context + security (cross-origin) | Own DOM (isolated from parent) | Only if cross-origin (own process under Site Isolation); same-origin work still competes | postMessage with mandatory event.origin check |
Untrusted UI: third-party widgets, embeds, ad creatives, comment boxes | Serialization overhead; same-origin/srcdoc frames give context isolation but little thread relief |
| Partytown | Thread + synchronous DOM proxy | Proxied (synchronous facade over the real DOM) | Yes — script executes in the worker | Internal proxy (service worker / SharedArrayBuffer + Atomics) |
Off-the-shelf tag managers and analytics that demand document/dataLayer |
Per-operation proxy latency; service-worker setup; wrong fit for interactive, latency-critical widgets |
The one-line rule: worker for compute you own, iframe for UI you don’t trust, Partytown for vendor tags that refuse to give up the DOM.
When the script is untrusted, remember that neither boundary removes the need for a restrictive strict Content Security Policy: worker-src governs where worker scripts may load from, frame-src governs iframe sources, and connect-src still gates every fetch() a worker or Partytown makes.
Verification
After you commit to a boundary, one trace confirms it did what you chose it for:
A single objective delta seals it: main-thread Total Blocking Time attributable to the vendor domain should drop toward zero for the worker/Partytown path, or stay flat (with the UI now sandboxed) for the same-origin iframe path.
Common Pitfalls
- Choosing a bare Web Worker for a DOM-dependent SDK. The vendor script throws
ReferenceError: document is not definedthe moment it evaluates. If it needs the DOM but you still want thread relief, that is precisely Partytown’s job — not a hand-rolled worker. - Assuming any iframe frees the main thread. A same-origin or
srcdociframe usually shares the parent renderer process; its synchronous work still blocks paint. Only a cross-origin frame reliably lands in its own process under Site Isolation. - Treating a worker as a security sandbox. A same-origin worker can
fetch()first-party endpoints with cookies and reach anythingconnect-srcallows. To contain untrusted code you need the origin boundary of a sandboxed iframe, not a worker.