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.

Web Worker boundary versus sandboxed iframe boundary Left: the page main thread and a Web Worker thread share one origin and exchange structured-clone messages; the worker has no DOM. Right: the parent browsing context and a sandboxed iframe context each hold their own DOM and event loop across an origin boundary, exchanging validated postMessage calls. Web Worker — thread boundary same origin Main thread DOM + paint event loop Worker thread no DOM own event loop postMessage structured clone relieves thread · no security wall Sandboxed iframe — context boundary Parent context own DOM origin A iframe context own DOM origin B origin wall postMessage verify event.origin security wall · thread relief only if cross-origin

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 defined the 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 srcdoc iframe 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 anything connect-src allows. To contain untrusted code you need the origin boundary of a sandboxed iframe, not a worker.

Up: Offloading Heavy Scripts to Web Workers