v2.0 alpha — React, Vue, Svelte adapters

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
0 KB
Framework runtime on main thread
60 fps
Frame-budgeted target
~21 KB
Core runtime (gzip)
React · Vue · Svelte
Framework adapters
GitHub stars npm weekly downloads npm version bundle size

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.

1

Write normal code

Use React, Vue, Svelte, or vanilla JS. The virtual DOM API is comprehensive — querySelector, classList, dataset, events, all of it.

2

Mutations are collected

Every createElement, appendChild, setAttribute is intercepted, batched, and coalesced in the worker.

3

Transported efficiently

A 22-opcode binary codec with string deduplication serializes mutations. Also supports postMessage and WebSocket.

4

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.

Worker Thread Main Thread VirtualDocument full DOM API MutationCollector batch + coalesce ThreadManager per-app routing FrameScheduler budget + priority DomRenderer(s) apply to real DOM EventBridge DOM → Worker SyncChannelHost Atomics.notify Transport binary / postMessage / WS Events Sync Reads SharedArrayBuffer + Atomics SyncChannel blocking reads

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.

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.