Architecture
How the Rust host, local Node runtime, and WebView renderer fit together.
Murasaki is a three-layer desktop runtime. React renders in the operating system WebView, application backend code runs in a bundled Node.js process, and a small Rust host owns the native window and application lifecycle.
┌──────────────── Native application ────────────────┐
│ │
│ Rust host (tao / wry / muda) │
│ windows · WebViews · native menus · update handoff│
│ │ │
│ │ launches / supervises │
│ ▼ │
│ Local Node runtime │
│ src/main.ts · use-main · use-server · API routes │
│ │ │
│ │ authenticated loopback HTTP│
│ ▼ │
│ OS WebView │
│ React 19 · Murasaki router · your renderer code │
│ │
└────────────────────────────────────────────────────┘This is deliberately different from Electron's embedded Chromium + Node
renderer model. Renderer code does not receive Node globals. Put filesystem,
database, socket, worker, and secret-bearing code in
src/main.ts, a 'use main' module, a
'use server' action, or an
src/api/ route.
Layer responsibilities
| Layer | Owns | Does not own |
|---|---|---|
| Rust host | Native event loop, declared application windows, per-window navigation/capability policy, OS open-request normalization, menus, Node child supervision, final update handoff | Business logic and React state |
| Local Node runtime | Long-lived main lifecycle, registered URL/file handling, actions, API routes, update verification, application resources and data | Arbitrary native window creation |
| Renderer | UI, routing, browser APIs, typed backend calls, permitted management of declared windows | Direct filesystem/Node/native module access |
Murasaki is pre-1.0. Declared windows have independent, deny-by-default
command allowlists, but capability arguments and path/URL scopes are not yet
policy-controlled. Packaged macOS/Windows apps enforce a single instance,
can register URL schemes and file associations, and expose matching
activations to Node Main through openRequested(). Check Platform & feature
status before committing a
production architecture to a feature.
Development and packaged runtime
The application code keeps the same boundaries in both modes, but the host processes differ:
murasaki dev | Packaged app | |
|---|---|---|
| Web assets | Vite dev server with HMR | dist/client served by bundled Node |
| Node backend | Vite child process | Portable Node runtime inside app resources |
| Native host | CLI process loads @murasakijs/native | Standalone Rust launcher |
| Declared windows | All routes created with individual dev WebViews | All routes created from resolved bundle metadata |
| Main entry | Loaded with Vite SSR; exact entry hot-reloads | Compiled to server/main.mjs |
| Backend calls | Vite middleware | Bundled loopback HTTP server |
| URL/file registration | Not installed by dev mode | macOS app metadata or Windows installer registry |
Production uses a deterministic loopback port derived from appId, giving the
renderer a stable origin across launches. The Windows WebView2 profile is also
isolated by appId. Keep appId stable after release: changing it can change
the application data paths, browser profile, bundle identity, single-instance
lock, and local origin.
Next.js-like, not Next.js itself
Murasaki adopts familiar conventions—src/app, layouts, route handlers,
directives, and React 19—but it is not the Next.js runtime. There are no React
Server Components or Edge runtime, and middleware only participates in client
navigation. Each guide documents the supported subset; do not assume an API is
available because it exists in Next.js.