DEV Community

Nikhil Ranjan
Nikhil Ranjan

Posted on

HTML Was a Miracle. Now It’s a Bottleneck. What if we started over?

I built a browser that doesn’t understand HTML. Here’s why.

I’ve spent over a decade building for the web. JavaScript, React, Node.js — the full stack. I’ve shipped products that served millions. I’ve watched the web evolve from jQuery spaghetti to TypeScript monorepos with 400MB node_modules directories.

And somewhere along the way, I started asking a question that felt almost heretical:

What if HTML was the wrong abstraction for the modern internet?

So I built Oxide — a browser that fetches and runs WebAssembly binaries instead of HTML documents. No DOM. No CSS parser. No JavaScript engine. Just compiled code, a canvas, and a capability-based sandbox.

This is the story of why I built it, what I learned, and how you can build apps for it today.

The Web Is a Document Viewer Pretending to Be an App Platform

HTML was invented in 1993 to share physics papers. Thirty-three years later, we use the same document markup language to build Figma, Google Sheets, and real-time multiplayer games.

Think about that.

Every time you open a modern web app, your browser:

  1. Downloads an HTML document it barely reads
  2. Parses a CSS stylesheet that fights with itself via specificity wars
  3. Downloads megabytes of JavaScript that immediately takes over
  4. Builds a DOM that JavaScript then destroys and rebuilds via a virtual DOM
  5. Runs a layout engine designed for flowing text, hacked to do grid-based UI
  6. Serializes everything as JSON — a text format — over the wire
  7. We’ve built an entire industry of tooling — bundlers, transpilers, tree-shakers, minifiers, source maps — just to make this Rube Goldberg machine tolerable.

The browser is the most sophisticated piece of software most people use daily. And most of its complexity exists to work around decisions made before the word “app” meant anything digital.

What If We Started Over?

Not “let’s fix the web.” Not “let’s add another spec to the pile.” What if we asked: if you were designing an application delivery platform today, with no backwards compatibility constraints, what would it look like?

Here’s my answer:

  • Binary, not text. Applications should arrive as compiled code, not markup that needs to be parsed, interpreted, and JIT-compiled.
  • Capability-based, not origin-based. Security shouldn’t depend on which domain served the bytes. It should depend on what explicit permissions the runtime grants.
  • Imperative rendering, not declarative documents. Apps should draw pixels, not describe document structure and hope a layout engine figures out what they mean.
  • Protobuf, not JSON. Data over the wire should be compact, typed, and fast to encode/decode — not a text format that requires quoting keys.

That’s Oxide.

Binary-First: The Core Thesis

Here’s the thing people miss about WebAssembly: it’s not just “fast JavaScript.” It’s a fundamentally different delivery model.

When a traditional browser loads a page, it downloads text, parses it into trees, resolves dependencies, compiles code on the fly, and eventually — maybe — starts rendering. Each step is a potential failure point. Each step adds latency.

When Oxide loads an app, it downloads a .wasm binary and instantiates it. That’s it. The binary is already compiled. There’s no parsing phase. No tree construction. No style resolution. No layout pass. The app calls drawing functions and pixels appear.

Traditional browser pipeline:
DNS → TCP → TLS → HTTP → HTML parse → CSS parse → JS parse →
JS compile → JS execute → DOM build → Style resolve →
Layout → Paint → Composite

Oxide pipeline:
DNS → TCP → TLS → HTTP → WASM instantiate → Execute → Paint

Every step you remove is latency you eliminate and complexity you don’t have to debug.

The Security Model Nobody Asked For (But Everyone Needs)

The web’s security model is fundamentally broken. CORS, CSP, SOP, XSS, CSRF — we have more acronyms for security problems than most fields have for their entire domain.

Why? Because the browser gives applications enormous implicit capabilities and then tries to claw them back with policies. A web page can, by default, make network requests, access cookies, run arbitrary code, and interact with dozens of browser APIs. Security is a game of whack-a-mole: deny this, restrict that, sanitize the other thing.

Oxide inverts this. A guest module starts with zero capabilities:

  • Zero filesystem access
  • Zero environment variable access
  • Zero raw network sockets
  • Fixed 16MB memory ceiling
  • 500 million instruction fuel limit

Every capability — drawing to the canvas, making an HTTP request, reading the clipboard — is an explicit host function that the runtime provides. The guest can only do what the host allows. There’s nothing to claw back because nothing was given by default.

This isn’t theoretical. It’s how operating systems have worked for decades. It’s how mobile apps work. The web is the outlier.

Building Your First Oxide App

Enough philosophy. Let’s build something.

Oxide apps are Rust libraries compiled to wasm32-unknown-unknown. The SDK provides safe wrappers around host capabilities. Here’s the full workflow:

  1. Set up
rustup  target  add  wasm32-unknown-unknown
Enter fullscreen mode Exit fullscreen mode
  1. Create your project
cargo  new  --lib  my-app
cd  my-app
Enter fullscreen mode Exit fullscreen mode
  1. Configure Cargo.toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
oxide-sdk = { path = "../oxide/oxide-sdk" }
Enter fullscreen mode Exit fullscreen mode

The cdylib crate type is critical — it tells the compiler to produce a standalone .wasm module.

  1. Write your app
use oxide_sdk::*;

#[no_mangle]
pub extern "C" fn start_app() {
    canvas_clear(22, 22, 35, 255);

    canvas_text(40.0, 50.0, 32.0, 255, 255, 255, "Welcome to Oxide");
    canvas_text(40.0, 90.0, 18.0, 160, 160, 180,
        "A binary-first browser for the post-HTML web");

    canvas_rect(40.0, 130.0, 300.0, 4.0, 100, 80, 220, 255);

    canvas_rect(40.0, 160.0, 160.0, 80.0, 45, 45, 70, 255);
    canvas_text(55.0, 195.0, 14.0, 180, 180, 200, "No DOM");

    canvas_rect(220.0, 160.0, 160.0, 80.0, 45, 45, 70, 255);
    canvas_text(235.0, 195.0, 14.0, 180, 180, 200, "No CSS");

    canvas_rect(400.0, 160.0, 160.0, 80.0, 45, 45, 70, 255);
    canvas_text(415.0, 195.0, 14.0, 180, 180, 200, "No JS Engine");

    log("App rendered successfully.");
}
Enter fullscreen mode Exit fullscreen mode
  1. Build and run
cargo  build  --target  wasm32-unknown-unknown  --release
Open Oxide, click Open File, select target/wasm32-unknown-unknown/release/my_app.wasm. 
Enter fullscreen mode Exit fullscreen mode

Done. Your app is running in a sandboxed binary runtime with zero web tooling.

No webpack. No babel. No npm. No package.json. No polyfills. One command. One binary.

Interactive Apps with on_frame
Static rendering is just the start. Export on_frame and Oxide calls it every frame, giving you an immediate-mode UI loop:

use oxide_sdk::*;

static mut COUNTER: i64 = 0;

#[no_mangle]
pub extern "C" fn start_app() {
    log("Interactive app loaded.");
}

#[no_mangle]
pub extern "C" fn on_frame(_dt_ms: u32) {
    canvas_clear(22, 22, 35, 255);
    let count = unsafe { COUNTER };

    canvas_text(40.0, 50.0, 28.0, 255, 255, 255,
        &format!("Count: {}", count));

    if ui_button(1, 40.0, 80.0, 100.0, 32.0, "+1") {
        unsafe { COUNTER += 1; }
    }

    if ui_button(2, 160.0, 80.0, 100.0, 32.0, "Reset") {
        unsafe { COUNTER = 0; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Buttons, checkboxes, sliders, text inputs — all available as immediate-mode widgets. No component lifecycle. No state management library. No re-render diffing. You draw your UI every frame and the runtime handles the rest.

Protobuf as the Native Wire Format
The web chose JSON as its lingua franca. JSON is human-readable, schema-less, and roughly 2–10x larger than equivalent binary representations. Every field name is repeated in every message. Every number is a string. Parsing it requires allocating strings and building hash maps.

Oxide ships with a built-in protobuf encoder/decoder — zero dependencies, wire-compatible with every protobuf implementation on Earth:

use oxide_sdk::proto::{ProtoEncoder, ProtoDecoder};

let msg = ProtoEncoder::new()
    .string(1, "nikhil")
    .uint64(2, 31)
    .bool(3, true);

let resp = fetch_post_proto("https://api.example.com/users", &msg)
    .unwrap();

let mut dec = ProtoDecoder::new(&resp.body);
while let Some(field) = dec.next() {
    match field.number {
        1 => log(&format!("id: {}", field.as_u64())),
        2 => log(&format!("status: {}", field.as_str())),
        _ => {}
    }
}
Enter fullscreen mode Exit fullscreen mode

Typed fields. Compact encoding. No key strings repeated per message. This is what network communication should look like between machines.

Dynamic Module Loading: The Binary script Tag

One .wasm module can load another at runtime:

let  result  =  load_module("https://cdn.example.com/widget.wasm");
Enter fullscreen mode Exit fullscreen mode

The child module shares the canvas and console but gets its own isolated memory and fuel budget. Think script tag but with real isolation. A malicious third-party widget can’t read your app’s memory. It can’t exhaust your fuel budget. The sandbox applies at every level.

Thought Experiments:

What if browsers had been binary-first from the start?

We wouldn’t have CSS specificity. We wouldn’t have !important. We wouldn’t need virtual DOMs because there would be no DOM. We wouldn’t have 2,000-line webpack configs. We wouldn’t need TypeScript because apps would be written in typed languages from day one. Front-end engineering would be indistinguishable from software engineering.

What if security was additive, not subtractive?

Instead of giving apps everything and trying to lock it down (and constantly failing — XSS is a 25-year-old bug class that still tops the OWASP list), what if apps started with nothing and explicitly requested capabilities? Mobile platforms proved this model works. Oxide applies it to the browser.

What if the browser was just a runtime?

Strip away the HTML parser, the CSS engine, the JavaScript JIT, the DOM, the layout engine. What’s left? A sandboxed execution environment that can render graphics and make network requests. That’s Oxide. That’s also, arguably, what the browser has been trying to become for two decades.

What if we measured progress by what we removed?

The tech industry celebrates addition. New frameworks. New build tools. New layers of abstraction. But the most impactful innovations often come from subtraction. Unix pipes. REST. WebAssembly itself. Oxide is an experiment in subtraction: what if we removed everything the modern web added to compensate for HTML’s limitations, and started from the capability that web apps actually need?

What Oxide Is Not

Let me be direct about what this is and isn’t.

Oxide is not a production browser. It’s not going to replace Chrome. It’s a proof-of-concept — a working argument for what the next era of application delivery could look like.

It doesn’t have accessibility APIs (yet). It doesn’t have text selection or find-in-page. It doesn’t have a robust layout engine because it deliberately doesn’t have a layout engine — that’s the point.

But every technology that changed the world started as a toy that “real engineers” dismissed. Unix was a toy. The web was a toy. WebAssembly was an experiment.

The Uncomfortable Question

Here’s what keeps me up at night:

We’ve spent 30 years optimizing a document viewer to behave like an application runtime. What if we’d spent those 30 years building an application runtime?

Oxide is my attempt at answering that question. It’s written in Rust, it’s open source, and it works today. Clone it, build an app, break it, tell me what’s wrong with it.

The best ideas survive contact with reality. Let’s find out if this one does.

Oxide is open source at:

https://github.com/niklabh/oxide
https://oxide.foundation/

Nikhil Ranjan is a senior software engineer based in Berlin. He builds scalable systems and publishes open source software. Find him on GitHub and X.

Top comments (0)