Run React, Vue & Svelte in Web Workers
Your framework runs off the main thread. Your content stays off the page source.
Move your entire UI framework into a Web Worker. The main thread receives only serialized mutations. Content is structurally invisible to scrapers, bots, and AI crawlers.
npm install @lifeart/async-dom
The web wasn't built for today's threats
Content theft is industrialized. Performance expectations are brutal. Traditional architecture exposes everything.
AI scrapers ignore your rules
Cloudflare blocked 416 billion AI bot requests in one year.
OpenAI's crawl-to-referral ratio is 1,700:1. robots.txt is voluntarily ignored.
Your content trains models that compete with you.
The main thread is a bottleneck
JavaScript is single-threaded. Your framework, your business logic, third-party scripts, and user input all fight for 16ms per frame. Modern devices have 4-8 cores. Traditional web apps use one.
Everything is exposed
Source code in bundles. Data structures in the DOM. Auth tokens accessible to XSS. Business logic readable by anyone with DevTools. Browser extensions with full access. Traditional architecture offers no isolation boundary.
Your app runs in a Worker. The DOM is a projection.
async-dom provides a virtual document and window inside a Web Worker.
Your framework thinks it's talking to the real DOM. It isn't.
Write normal code
Use React, Vue, Svelte, or vanilla JS. The virtual DOM API is comprehensive — querySelector, classList, dataset, events, all of it.
Mutations are collected
Every createElement, appendChild, setAttribute is intercepted, batched, and coalesced in the worker.
Transported efficiently
A 22-opcode binary codec with string deduplication serializes mutations. Also supports postMessage and WebSocket.
Applied at 60 fps
A frame-budgeted scheduler on the main thread applies mutations with priority sorting and viewport culling. Targets 60 fps with graceful degradation under heavy load.
Three lines to move your UI off the main thread
Works with any framework. No rewrites needed.
// main.ts import { createAsyncDom } from "@lifeart/async-dom"; const dom = createAsyncDom({ target: document.getElementById("app"), worker: new Worker(new URL("./worker.ts", import.meta.url), { type: "module" }), }); dom.start(); // worker.ts import { createWorkerDom } from "@lifeart/async-dom/worker"; const { document } = createWorkerDom(); const div = document.createElement("div"); div.textContent = "Hello from a Web Worker!"; document.body.appendChild(div);
import { AsyncDom } from "@lifeart/async-dom/react"; function App() { return ( <AsyncDom worker="./app.worker.ts" debug fallback={<div>Loading...</div>} onReady={(instance) => console.log("ready")} onError={(err) => reportError(err)} /> ); }
<!-- App.vue --> <template> <AsyncDom worker="./app.worker.ts" :debug="true" @ready="onReady"> <template #fallback><div>Loading...</div></template> </AsyncDom> </template> <script setup> import { AsyncDom } from "@lifeart/async-dom/vue"; </script>
<!-- App.svelte --> <script> import { asyncDom } from "@lifeart/async-dom/svelte"; </script> <div use:asyncDom={{ worker: "./app.worker.ts" }} />
Built for every stakeholder
Different roles, different priorities. async-dom addresses them all.
Structural defense, not configuration
Worker isolation is enforced at the browser engine level. No shadowRoot workaround,
no extension bypass, no polite requests to behave. This is architecture, not policy.
-
XSS containment. Main-thread scripts cannot access worker-scoped variables or closures. All data passes through postMessage — a natural serialization boundary. Note: XSS on the main thread can still read the rendered DOM.
-
Token isolation. Auth tokens and session state stored as worker-scoped variables are inaccessible to main-thread XSS. Note: tokens in HTTP request headers remain visible to browser DevTools and extensions with appropriate permissions.
-
Anti-scraping by architecture. Empty HTML payload. Procedural DOM construction. Architecture supports per-session DOM variation. Raises the cost of scraping from HTTP parsing to full browser automation — an architectural property, not a bolted-on defense.
-
Built-in sanitization. HTML sanitizer strips scripts, iframes, on* handlers, javascript: URIs. Property allowlist. Attribute filtering. Defense in depth.
-
Bot detection at the app layer. The worker controls what it renders and when, enabling application-level bot detection patterns. This is an application-level capability, not a built-in feature.
Ship faster. Scale smarter. Sleep better.
One library. Three framework adapters. Zero framework runtime on the main thread. Multi-core utilization without the complexity of manual worker management.
-
Core Web Vitals improvement. Moving framework execution off the main thread can improve INP and TBT when JavaScript computation is the bottleneck. Event round-trips add 2-20ms of latency compared to same-thread handlers.
-
True micro-frontend isolation. Each team ships a worker. CSS is encapsulated via shadow DOM. No shared globals. Independent deployment. React 18 and React 19 on the same page — zero conflicts.
-
Remote rendering via WebSocket. Run the app on a server, stream DOM updates to Smart TVs, kiosks, IoT devices, or thin clients. One codebase, any screen.
-
Protect proprietary UI logic. NDA demos, premium features, competitive algorithms — keep them in a worker or on a server, not in client bundles.
-
1,483 tests. TypeScript. MIT license. Production-ready with strict types, Biome linting, CI pipeline, Vite plugin, CLI scaffolding, and Chrome DevTools extension.
Write normal code. It just runs in a Worker.
No new framework to learn. The virtual DOM API is comprehensive — your existing React, Vue, or Svelte code works without modification. First-class DX with DevTools, error reporting, and hot reload.
-
Framework adapters for React, Vue & Svelte. <AsyncDom> component for React/Vue. use:asyncDom action for Svelte. useAsyncDom hooks/composables. Fallback, onReady, onError — all wired up. Some APIs that directly access the browser DOM require adaptation.
-
Built-in DevTools panel. 5-tab debug panel: virtual DOM tree with inspector, frame-budget flamechart, P50/P95/P99 latency, mutation log with time-travel replay, causality graph.
-
Synchronous DOM reads. getBoundingClientRect(), offsetWidth, getComputedStyle() return real values via SharedArrayBuffer. No async workarounds.
-
Vite plugin with zero config. COOP/COEP headers, binary transport in production, worker error overlay, debug defines — all automatic.
-
CLI scaffolding.
npx @lifeart/async-dom init my-app --template react-ts— running in under a minute with vanilla, React, or Vue templates. -
Sandbox mode for third-party scripts. Patch worker globals or use Proxy + eval sandbox. Third-party analytics, ads, and widgets "just work" inside the worker.
Faster pages. Better rankings. Protected content.
Google uses Core Web Vitals as a ranking signal. async-dom moves heavy computation off the main thread, directly improving the metrics that matter for SEO.
-
Better INP and TBT. Interaction to Next Paint and Total Blocking Time improve when the main thread isn't running your framework. Google measures this.
-
Real-time UX broadcasting. WebSocket transport lets you observe exactly what users experience, live. Session replay at the mutation level, not pixel approximation.
-
Content stays yours. AI crawlers that rely on HTTP responses get an empty shell. Headless browser-based crawlers must execute the full worker pipeline, raising extraction cost and complexity.
-
Server-controlled rendering. The worker controls what content is rendered and when. Per-session content variation and server-side rendering gates are architectural capabilities the framework supports.
-
Collaborative demos. Multi-user events via WebSocket. Build interactive product demos where multiple users interact with the same live UI.
Technical enforcement for legal rights
The NYT sued OpenAI. The IAB proposed the AI Accountability for Publishers Act. But legal remedies are slow. async-dom gives your legal team a technical backstop.
-
Content never in source. Content is not present in the initial HTML payload or JavaScript bundles. Extraction requires executing the page in a browser environment and reading from the live DOM, raising the cost of automated extraction.
-
Per-session content variation. The architecture supports per-session rendering variation, enabling content fingerprinting patterns at the application layer. This is a capability the architecture facilitates, not a built-in feature.
-
Access control at render time. The worker can enforce application-level rendering rules: time-limited viewing, user-specific content, geographic variation — complementing server-side access controls.
-
Copy-paste control. Clipboard operations pass through the worker's abstraction layer. Intercept, modify, or log copy events without degrading the reading experience.
Live demos running entirely in Web Workers
Every demo below runs its full application logic in a worker. The main thread only applies DOM mutations. Open DevTools — there's zero framework code on the main thread.
Framework Showcase: React + Vue + Svelte
Three workers running simultaneously — Mandelbrot, Game of Life, and Particle simulation — each in its own shadow DOM. Zero framework runtime on the main thread.
7,000 Nodes Grid
Interactive color grid with 7,000 DOM nodes. Click to score, hover to highlight. All at 60fps from a worker.
React: Mandelbrot Explorer
Interactive fractal renderer — 4,800 pixels computed in a worker. Click to zoom into infinite detail.
Vue: Game of Life
Conway's Game of Life on a 60x40 grid. 2,400 cell DOM updates per generation, all in a worker.
Svelte: Particle Life
320 particles with attraction/repulsion rules. Emergent behaviors rendered on a 60x60 grid.
Multi-App Isolation
Two worker apps in separate shadow roots with fully isolated CSS. Color picker + stopwatch side by side.
Todo List
Input sync, keyboard events, dynamic add/remove, classList toggling. The classic demo, from a worker.
Counter
The simplest possible example. Two buttons, a number, click events. Under 20 lines of worker code.
Audio Player
Play, pause, seek — all controlled from a worker. Media API bridging via callMethod mutations.
DevTools Panel
The 7,000-node grid with the built-in 5-tab debug panel. Tree inspector, flamechart, mutation log, causality graph.
Ready to try it?
npx @lifeart/async-dom init my-app
Templates: vanilla-ts · react-ts · vue-ts
How async-dom compares
There are other projects in the worker-DOM space. Here's how they differ.
| Feature | async-dom | Partytown | worker-dom (AMP) |
|---|---|---|---|
| Scope | Full app rendering | Third-party scripts | AMP components |
| Framework support | React, Vue, Svelte, vanilla | N/A | AMP only |
| DOM API coverage | Broad | Proxy forwarding | Subset |
| Sync DOM reads | SharedArrayBuffer | Service Worker + Atomics | No |
| Frame budgeting | Adaptive + priority | No | No |
| Binary wire protocol | 22 opcodes + string dedup | No | Transfer list |
| Multi-app isolation | Shadow DOM | No | No |
| WebSocket transport | Yes (remote rendering) | No | No |
| Content protection | Structural | No | No |
| Built-in DevTools | 5-tab panel + Chrome ext | No | No |
| Bundle size (gzip) | ~21 KB total | ~12 KB | ~12 KB |
| Project status | Active | Maintenance | Inactive |
Beyond performance and security
Copyright & DRM enforcement
The NYT sued OpenAI. Content creators need technical enforcement, not just legal remedies. async-dom keeps content in the worker — the DOM receives rendering instructions, not your data model. The architecture supports per-session content variation for fingerprinting.
Micro-frontend isolation
Each team ships a worker. CSS is encapsulated via shadow DOM. No shared globals. Run React 18 and React 19 side by side — zero conflicts, no iframes.
Remote & IoT rendering
Run the app on a server. Stream DOM updates via WebSocket to Smart TVs, kiosks, or budget phones. One codebase, any screen. The device only needs a thin renderer.
Common questions
Won't Google fail to index an empty HTML page?
async-dom is designed for authenticated app shells, not public marketing pages. Use it for dashboards, admin panels, premium content, and interactive apps — behind a login or where indexing is unwanted. For SEO-critical public pages, keep server-side rendering and apply async-dom selectively to the interactive parts.
What happens without SharedArrayBuffer (no COOP/COEP headers)?
Everything works — rendering, events, mutations, multi-app isolation. Only synchronous DOM reads
(getBoundingClientRect(), offsetWidth, getComputedStyle()) fall back to
default values. Most applications don't need sync reads. The Vite plugin adds the headers automatically for dev.
Does it work with third-party scripts (Stripe, analytics, etc.)?
Third-party scripts that need real DOM access (Stripe Elements, Google Maps) should stay on the main thread.
async-dom's per-app isolation means they coexist naturally — your worker app renders alongside main-thread widgets.
Analytics scripts can run in the worker via sandbox: "global" mode.
What's the event round-trip latency?
Typical postMessage round-trip is under 1ms. The binary transport adds negligible overhead. For most interactions (clicks, form input, navigation) this is imperceptible. For sub-frame-budget interactions (drag-and-drop, canvas painting), keep those handlers on the main thread and let async-dom handle the rest of the UI.
How is this different from an iframe?
Iframes create separate browsing contexts with independent rendering pipelines, heavy memory overhead, and limited communication. async-dom workers share the same rendering pipeline, communicate via zero-copy binary messages, and render into the parent document's DOM (or shadow DOM). No cross-origin restrictions, no layout limitations, dramatically lower overhead.
Can I migrate incrementally?
Yes. Use addApp() to mount one worker-powered component alongside your existing main-thread code.
Migrate section by section. Each worker app is isolated — it doesn't affect the rest of your page.
Does this work with Next.js, Nuxt, or SvelteKit?
async-dom replaces the rendering layer, not the routing or data-fetching layer. It is not a drop-in for meta-frameworks. Use it for standalone apps, dashboards, embedded components, or protected content applications where SSR-based routing is not required.
Is content accessible to screen readers?
Yes. async-dom applies mutations to the real DOM. The rendered output is identical to what a traditional app produces. ARIA attributes, semantic HTML, roles, and all accessibility features work as expected.
Who is using this in production?
async-dom is in active development. We welcome early adopters building dashboards, internal tools, protected content applications, and interactive demos. If you ship with async-dom, we'd love to hear about it.
What is the input latency overhead?
Event round-trips (main thread → worker → main thread) add 2-20ms of latency compared to same-thread handlers, depending on mutation complexity and postMessage serialization. For most interactive UIs this is imperceptible. For sub-millisecond interactions (e.g., drag-and-drop, canvas drawing), consider keeping that logic on the main thread.
Scaffold a project now
npx @lifeart/async-dom init my-app --template react-ts
MIT licensed. TypeScript strict. 1,483 tests. React, Vue, Svelte adapters. Vite plugin. Chrome DevTools extension.