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.
Windows, WebViews, native menus, lifecycle, and update handoff
src/main.ts · use-main · use-server · API routes
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, configured window template catalog, 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, create/destroy of configured secondary templates, registered URL/file handling, actions, API routes, update verification, application resources and data | Arbitrary URLs or runtime capability policies for native windows |
| 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. URL, filesystem path, target-window, and OS-permission
arguments support explicit allow/deny scopes; other command arguments remain
command-level. 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 | Full template catalog passed to the native host; only createOnLaunch templates start immediately | Full catalog loaded from bundle metadata; only createOnLaunch templates start immediately |
| 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 begins with a deterministic loopback port derived from appId and
persists the port that actually binds. If another application already owns the
first choice, the bundled server selects a bounded fallback and reuses it on
later launches. This keeps the renderer origin stable without making unrelated
hash-colliding apps mutually exclusive. Keep appId stable after release:
changing it can change application data paths, browser profiles, bundle
identity, the single-instance lock, and the local origin.
Each window label owns a distinct WebContext. Service Workers, SharedWorkers,
cookies, and Web Storage from a secondary window therefore cannot observe the
primary window's authenticated backend traffic. Recreating the same label
reuses its context within the process; persistent profile behavior across
launches is platform-dependent. On macOS 11–13, secondary labels use isolated
non-persistent stores because custom persistent stores require macOS 14.
webview.incognito: true makes every label non-persistent.
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.