Articles

How Browser-First Runtimes Actually Work

A practical tour of the browser primitives that make a real development loop possible inside a tab.

Browser-first runtimes are easy to describe badly. "Node.js in the browser" sounds like one large binary compiled to WebAssembly, or a thin iframe around a remote container. In practice, the useful version is more deliberate: use the browser as the first host, use its native isolation and storage primitives, and move to a server only when the workload actually needs server capabilities.

Verklet is built around that shape. It runs Node.js-style workloads and supported Python in the visitor's tab first. Web Workers isolate processes, WebAssembly handles the byte-heavy hot paths, OPFS persists project state, and an isolated preview bridge makes previews feel like normal pages without sharing your app origin.

The runtime is a set of browser services

The browser does not expose a POSIX process table, a loopback network, or a normal filesystem. A browser-first runtime has to create those interfaces from the primitives the platform does expose.

The important pieces are:

Each piece is ordinary by itself. The product work is making them behave like one runtime.

Workers become processes

Every spawned command runs in a worker boundary. That is the difference between embedding a code editor and embedding a development environment.

When user code throws, loops, or exits, it should not freeze the page that owns the editor. The main thread should keep rendering the terminal, the file tree, and the preview frame. A worker gives each runtime process its own execution boundary, stdio stream, and lifecycle events.

That also makes parallel work possible. A tutorial can run a dev server while a build script runs. An agent can edit files, run tests, and inspect output without turning the host UI into the runtime.

The filesystem has to feel synchronous

Node packages expect fs.readFileSync, CommonJS resolution, require(), and lots of small filesystem checks. Browser storage APIs are mostly asynchronous. That mismatch is one of the core reasons browser runtimes are hard.

Verklet uses a virtual filesystem owned by the runtime, with WebAssembly on the byte-heavy paths. The goal is not to expose OPFS directly to user code. The goal is to provide filesystem semantics that Node-compatible tools can use, then persist snapshots to OPFS when a persistenceKey is configured.

That snapshot model gives the host product a useful property: reload the page, mount the previous files, and continue. The runtime can degrade when OPFS is missing, but the programming model stays the same.

Previews need routing, not public ports

A browser worker cannot open a real TCP listener on localhost:5173. But embedded development environments still need previews. If a Vite app or a small HTTP server responds to a request, the user should see that response in an iframe.

The browser-friendly answer is preview routing. Verklet uses a dedicated preview origin and routes preview URLs into the runtime. The iframe loads a normal URL on preview.verklet.com; the preview bridge turns that request into a message to the runtime process, then returns the response.

That keeps untrusted preview code away from the application origin while still behaving like a normal browser preview rather than a remote tunnel.

Python starts in the tab too

Python can also be browser-first when the workload fits Pyodide. A runtime can run supported python, python3, pip, and pip3 flows without provisioning a backend for every visitor.

The boundary matters. Pyodide is excellent for supported packages, notebooks, plots, and educational code. It is not a substitute for every native wheel, subprocess, uv workflow, or Linux tool. That is why a hybrid runtime needs a server path.

Server execution should be promotion, not a separate product

The useful hybrid model is not "browser runtime over here, remote container over there." It is one SDK surface with different backends.

With backend: 'auto', Verklet starts in the browser and promotes when a workload needs server capabilities. Promotion exports the browser filesystem snapshot, creates a managed server session, mounts the snapshot into that workspace, and continues through the same runtime object.

The host UI still calls runtime.fs, runtime.spawn(), and runtime.on(...). The backend changed because the workload required it; the application code did not split into two runtime integrations.

The browser is the default because it changes the economics

Containers are powerful, but they are an expensive default for every demo, tutorial, preview, and agent scratchpad. Browser-first execution changes the cost and latency profile:

That is the practical definition of a browser-first runtime. It is not a claim that the browser is the right host for everything. It is a runtime that starts with the cheapest capable host and moves only when the work demands it.