xread: The Web-to-LLM Compiler That Chooses the Right Browser Only When It Has To

A self-hosted fork that strips out cloud dependencies, adapts between curl, JSDOM, and Puppeteer, and turns messy pages into clean Markdown or JSON for RAG pipelines.

9 min read • View on GitHub • More from xixu-me

A wide editorial scene shows a chaotic web page with popups, ads, and fragmented content entering a mechanical selector with three routes. The leftmost route is raw fetch, the middle route is DOM parsing, and the rightmost route is a browser, while clean Markdown and JSON pages emerge on the far side. It explains that xread is a routing engine for extraction, not a single scraping mode.
xread treats extraction as a routing problem. It picks the lightest path that still preserves meaning, then packages the result for downstream LLM use.
Key Takeaways

The web is a hostile input format for LLMs. Pages arrive with navigation chrome, script-heavy layouts, PDFs, ads, and half the content hidden behind JavaScript. xread exists to solve that mismatch without handing the job to a hosted service.

The web is not one problem

That sounds obvious until you try to ingest the open web at scale. A static article, an SPA, and a login-protected page are three different extraction problems wearing the same URL shape. xread starts from that premise and refuses to treat them all the same.

A close-up workshop scene shows a cluttered DOM tree entering a formatter bench and emerging as a clean document. Navigation bars, sidebars, and chrome are peeled away like packaging, while tables, image alt text, headings, and readable paragraphs survive the transformation. It explains that the formatter preserves semantic structure instead of merely stripping tags.
The formatter is the part that turns a page into usable context. It keeps the structure LLMs care about and discards the noise that wastes tokens.

xread’s real superpower is choosing the cheapest path that still works

The clever part is not extraction itself. It is the runtime choice. xread can start with a raw fetch, move to JSDOM plus Readability for simpler pages, and escalate to Puppeteer only when the page really needs a browser.

The pipeline is adaptive, not fixed. xread chooses the lightest route that preserves meaning and only pays browser cost when the page forces it.

const snapshot = await crawler.crawl(url, {
  cookies,
  scripts,
  output: 'markdown'
})

// The runtime can decide whether this becomes curl, JSDOM, or Puppeteer
// before the formatter turns the result into LLM-ready context.

That choice is the project’s main engineering claim. A browser-only scraper pays the heaviest cost every time. A dumb fetcher is cheaper, but it breaks the moment the page moves behind client-side rendering. xread sits in the middle and makes the trade-off explicit.

The fork is the product

xread is also a decoupling story. The standalone release removes internal infrastructure assumptions from the original Jina Reader lineage and replaces them with local roots like STORAGE_ROOT and LOCAL_DB_ROOT. In practice, that turns a useful system into something you can actually deploy.

DimensionHosted scraping APIGeneric fetch-and-convertxread
DeploymentVendor-managedSelf-run, but shallowSelf-hosted and local-first
AdaptabilityOpaque rulesUsually one extraction modeChooses curl, JSDOM, or Puppeteer
PrivacyData leaves your stackDepends on implementationKeeps control in your environment
Cost controlUsage-basedCheap, but brittle on complex pagesPays browser cost only when needed
OutputUsually normalizedOften plain text onlyMarkdown and JSON with preserved structure

That is why the fork matters. It is not just a license story. It is a deployment story. The codebase stops depending on hidden services and starts behaving like infrastructure you can own.

Why the service split matters

The repository does not expose one monolithic mode. It splits into crawl, search, and serp entry points, then narrows the RPC registry so each service stays scoped. That keeps the codebase unified without making the product feel bloated.

// Conceptual shape of the service split
crawl  -> single URL extraction
search -> LLM-friendly search text
serp   -> structured search JSON

// Each mode can selectively disable registry tags
// so the exposed surface stays narrow.

This is a discipline signal, not a feature list. The same codebase can support multiple jobs, but each job still gets its own contract. That matters when the tool is meant to sit in a pipeline instead of a demo.

What xread beats, and what it still is not

xread is strongest when you want local control, adaptive extraction, and output that still resembles the original page structure. It is weaker when you only need the simplest one-liner or when you are perfectly happy handing the whole problem to a hosted API.

Tool typeBest forWeak spot
Hosted scraping APIFast onboarding and managed opsLess control over data path and runtime choices
Bare HTML fetcherSimple static pagesBreaks on JS-heavy or heavily structured pages
Browser-only extractorMaximum fidelity on dynamic sitesHigh cost even when a lighter path would work
xreadSelf-hosted RAG ingestion with adaptive fidelityMore moving parts than a trivial fetch script

That is the honest center of the project. xread is not the lightest possible answer to every web page. It is the answer when you care about preserving meaning, choosing the cheapest viable path, and keeping the stack in your own environment.

The production posture

The maturity signals are real: strict TypeScript, integrity checks, audit scripts, Docker, Caddy, and dependency overrides. That is the shape of infrastructure that expects to be deployed, not just cloned.

In other words, xread is not a novelty scraper. It is a piece of plumbing for teams that want web context without surrendering control over the fetch path, the parser, or the output format.