Runtime
WebJs runs on Node 24+ or Bun. The same app source runs on either; the framework picks a runtime-neutral path internally and only the listener shell, the type stripper, and a few built-ins differ. Deno is a planned target (the listener seam is already runtime-neutral), not yet supported. This page is the single reference for the per-runtime commands and differences; other pages link here rather than repeat them.
Related reading:
- Getting Started for scaffolding an app.
- No-Build Model for how source is served.
- Deployment for shipping a container.
Node vs Bun at a glance
| Area | Node 24+ | Bun | Deno |
|---|---|---|---|
| Install | npm install | bun install (required, like Node) | planned |
| Run | npm run dev / npm run start | bun run dev / bun run start | planned |
| Listener | node:http shell | native Bun.serve (about 1.9x req/s on the listening path) | planned |
| TypeScript stripping | built-in module.stripTypeScriptTypes | amaro | planned |
| SQLite driver | built-in node:sqlite | built-in bun:sqlite | planned |
| Hot reload | node --watch | bun --hot | planned |
| WebSocket | the ws library | native Bun.serve (bridged to the same API) | planned |
| 103 Early Hints | yes | no (Bun.serve has no informational-response API) | planned |
| Dev edit to a page / layout | full reload (the node --watch restart replaces the process) | refreshes in place, no reload | planned |
The in-place dev refresh needs the server process to survive the edit, which is the whole of that last row. A page or layout never hydrates, so a freshly rendered page is the complete truth for it and the client router can swap it in without a reload, keeping your scroll position and the hydrated state of components outside the changed region. The server classifies the changed file and puts the verdict on the live-reload event, so it needs a process that is still alive to do the classifying.
Bun's bun --hot invalidates modules in place without restarting, so it gets the refresh. On Node, node --watch restarts the process on a change under app, components, modules, lib, or actions, or to a root middleware file, and a fresh process holds no record of what changed, so those edits are a full reload. Two Node cases still refresh in place: an edit outside that watched set (db/schema.server.ts, a webjs.dev.watch content directory), and npm run dev -- --no-hot, which keeps the server in one process on either runtime. A component edit is a full reload everywhere by design, because customElements.define is once-per-tag and swapping fresh markup onto the old class would be worse than the reload.
Either way the .ts stripping is position-preserving with no sourcemap, and the bytes the browser fetches are identical. The 103 Early Hints gap only costs a small first-load latency edge where your edge forwards 103, never correctness (the modulepreload hints still ship in the document head).
Node (the default)
Scaffold with webjs create my-app (Node is the default runtime). Then:
npm install
npm run dev # or: npm run start Node 24+ is required: the built-in TypeScript stripper (module.stripTypeScriptTypes, stable from Node 24) and recursive fs.watch need it. The CLI's assertNodeVersion() preflight enforces the floor.
Bun
Scaffold with webjs create my-app --runtime bun, or bun create webjs my-app (the runtime is auto-detected from the invoking package manager). Then:
bun install
bun run dev # or: bun run start A Bun app installs with bun install (like Node), then runs on Bun: its dev / start / db scripts force bun --bun, which overrides the webjs bin's Node shebang so the server runs on Bun, where it selects the native Bun.serve listener and strips types via amaro. The dependencies resolve from node_modules, the same as Node.
The install also gives editor type intelligence (the editor reads the .d.ts files in node_modules).
Install model and reproducibility
A Bun app commits a bun.lock (the Bun analog of package-lock.json) for reproducible, offline installs. The scaffold's Bun Dockerfile runs bun install and serves on Bun via CMD ["bun", "--bun", "run", "start"].
Future runtimes
The server's listener selection is a runtime-neutral seam: startServer chooses the Bun.serve shell on Bun and the node:http shell on Node through the same seam, which is designed to also host a Deno.serve or an embedded adapter later. When Deno support lands it will appear here. Edge runtimes with no filesystem are a separate, later target.