Skip to main content

react-native-nitro-jsdom

A headless HTML/DOM environment for React Native, powered by Nitro Modules, Lexbor, and QuickJS.

The Problem

React Native has no native equivalent of jsdom. When you need to parse an HTML document, manipulate its DOM, or run arbitrary JavaScript inside it, without rendering anything on screen, your only option today is a WebView, which:

  • Is a visual component, forcing it into the React tree
  • Makes headless / logic-only use cases impossible to isolate cleanly
  • Ties your business logic to the UI layer
  • Has high overhead: a full browser engine just to evaluate a script

A concrete real-world example: apps that render CMS-driven content widgets, HTML payloads with a small embedded script that computes a value (a countdown timer, a personalized greeting, a discount badge) before it's shown. On the web that script just runs inside an <iframe>. On React Native, developers are forced to use a hidden WebView, a fundamentally broken abstraction that drags the entire UI layer into what should be pure logic.

react-native-nitro-jsdom solves this by providing a real, isolated HTML + JS sandbox that runs entirely off-screen and off the React tree.

What It Does

import { JSDOM } from 'react-native-nitro-jsdom'

const dom = JSDOM.create(`
<html>
<body>
<div id="result">0</div>
</body>
</html>
`)

// Mutate the DOM via evaluate(), the only DOM access path
await dom.evaluate(`
document.getElementById('result').textContent = String(2 + 2)
`)

// Read values back via evaluate()
const value = await dom.evaluate(`document.getElementById('result').textContent`)
// → "4"

// Get the final HTML
const html = dom.serialize()

// Free native memory
dom.dispose()

evaluate() is the single door into the sandbox: all DOM queries and mutations are expressed as JavaScript strings passed to QuickJS, which delegates to Lexbor internally. The DOM shape mirrors jsdom (querySelector, textContent, dataset, and more), though usage differs: everything runs through the async, string-based evaluate(), not jsdom's synchronous new JSDOM() + direct dom.window.document access.

Comparison

FeatureWebView (hidden)react-native-nitro-jsdom
Requires React componentYesNo
Runs headless / off-treeNoYes
Isolated instancesComplexNative
Arbitrary JS executionYesYes
Full DOM APIYesProgressive
Memory controlLimitedFull (dispose())
Performance overheadHighLow