Routa's Dual-Backend Architecture: Next.js Web vs Tauri Desktop Explained
Routa implements a unified workspace-first platform that runs identical domain logic on both a Next.js web runtime and a native Tauri desktop runtime, ensuring seamless API parity through a shared OpenAPI contract.
Routa is a multi-agent coordination platform developed in the phodal/routa repository designed to operate as a workspace-first environment across multiple surfaces. Its dual-backend architecture leverages Next.js for server-rendered web deployments and Tauri with Axum for standalone desktop binaries, both adhering to the single source of truth defined in api-contract.yaml. This design guarantees that workspaces, sessions, and tasks maintain identical semantics whether accessed via browser or native application.
Architectural Overview: One Domain, Two Runtimes
Routa deliberately avoids treating web and desktop as separate products. Both runtimes share the same domain models located in src/core/models/ and communicate via REST/SSE endpoints specified in the repository's OpenAPI contract. The web runtime executes inside a Node.js process managed by Next.js, while the desktop runtime bundles a Rust-based Axum HTTP server that serves the same API shape locally.
Web Backend: Next.js Integrated Server
The web backend operates from the src/app/ directory using Next.js pages and API routes. When running in a browser, API requests such as fetch('/api/workspaces') resolve to handlers under src/app/api/workspaces/. These handlers execute within the same Node process as the frontend, manipulating the in-memory model through TypeScript services in src/core/.
This approach supports Server-Side Rendering (SSR) and Vercel-compatible deployments without requiring external backend infrastructure. The entire stack remains purely JavaScript/TypeScript, with Next.js handling both the UI layer and API route resolution.
Desktop Backend: Tauri Shell with Axum HTTP Server
The desktop backend resides in apps/desktop/ and packages a Tauri application shell that launches a bundled Chromium window. Unlike the web version, the desktop runtime spawns a native Axum HTTP server compiled from the Rust crate crates/routa-server/. This server listens on http://127.0.0.1:3210 by default and implements the exact same routes defined in api-contract.yaml.
The Axum handlers—such as list_workspaces in crates/routa-server/src/api/workspaces.rs—manipulate shared domain models, enabling local-first file-system access and native APIs through Tauri's Rust bridge. The desktop binary bundles both the UI and the Axum server into a single distributable available via GitHub Releases.
The Shared API Contract
Both runtimes expose identical REST/SSE endpoints governed by api-contract.yaml located at the repository root. This OpenAPI definition serves as the shared boundary, preventing API drift between platforms. Because the API shape remains constant, agents utilizing ACP, MCP, or A2A protocols function interchangeably across web and desktop surfaces without modification.
Transparent Client-Side Routing
Routa abstracts runtime differences through two key utilities in the client layer. The resolveApiPath function in src/client/config/backend.ts detects the current environment and generates the appropriate base URL:
// src/client/config/backend.ts
import { isDesktop } from './env';
export function resolveApiPath(path: string): string {
const trimmed = path.startsWith('/') ? path : `/${path}`;
return isDesktop
? `http://127.0.0.1:3210/api${trimmed}`
: `/api${trimmed}`;
}
The desktopAwareFetch utility in src/client/utils/diagnostics.ts wraps standard fetch calls, automatically rewriting URLs to target the local Axum server when running inside the Tauri shell:
// src/client/utils/diagnostics.ts
export async function desktopAwareFetch(input: RequestInfo, init?: RequestInit) {
const url = typeof input === 'string' ? input : input.url;
const finalUrl = url.startsWith('/api')
? resolveApiPath(url)
: url;
return fetch(finalUrl, init);
}
Components use desktopAwareFetch to ensure network requests resolve correctly regardless of runtime, as shown in this universal workspace loader:
// Works in both runtimes
import { desktopAwareFetch } from '@/client/utils/diagnostics';
export async function loadWorkspaces() {
const resp = await desktopAwareFetch('/api/workspaces');
return resp.json();
}
Platform-Specific Bridges and Native Capabilities
While the API surface remains unified, environment-specific functionality abstracts through platform bridges. The src/core/platform/web-bridge.ts and src/core/platform/tauri-bridge.ts files isolate capabilities like file-system access. According to the phodal/routa source code, the Tauri bridge enables the desktop client to call into the Axum server's native capabilities, whereas the web bridge relies on browser APIs.
Runtime Execution Flows
Understanding the dual-backend architecture requires examining how requests flow through each runtime.
Web Execution Flow:
- Browser calls
fetch('/api/workspaces') - Next.js routes the request to
src/app/api/workspaces/ - TypeScript handler processes the request using in-memory models
- Response returns through the Next.js API layer
Desktop Execution Flow:
- Tauri UI calls
desktopAwareFetch('/api/workspaces') - Helper detects desktop environment and rewrites URL to
http://127.0.0.1:3210/api/workspaces - Request routes to the Axum handler in
crates/routa-server/src/api/workspaces.rs - Rust handler processes using the same domain models as the web side
// crates/routa-server/src/api/workspaces.rs
#[utoipa::path(
get,
path = "/api/workspaces",
responses((status = 200, description = "List of workspaces"))
)]
pub async fn list_workspaces(State(state): State<AppState>) -> impl IntoResponse {
let ws = state.workspace_service.list().await;
Json(ws)
}
Key Files in the Architecture
| File Path | Purpose |
|---|---|
src/app/ |
Next.js pages and API routes (web entry) |
apps/desktop/ |
Tauri configuration and packaging (desktop entry) |
crates/routa-server/src/lib.rs |
Axum server bootstrap and route initialization |
api-contract.yaml |
Shared OpenAPI definition consumed by both backends |
src/client/config/backend.ts |
URL resolution logic for environment detection |
src/client/utils/diagnostics.ts |
Fetch wrapper that transparently routes to correct backend |
src/core/platform/tauri-bridge.ts |
Native API abstraction for desktop runtime |
src/core/platform/web-bridge.ts |
Browser API abstraction for web runtime |
src/core/models/ |
Domain models shared across both runtimes |
README.md |
Architecture section detailing the dual-backend design |
Summary
- Routa's dual-backend architecture maintains a single domain model shared between Next.js web and Tauri desktop runtimes.
- The web backend uses Next.js API routes in
src/app/running within a Node.js process. - The desktop backend bundles an Axum Rust server (
crates/routa-server/) listening on127.0.0.1:3210inside a Tauri shell. - Both backends implement identical endpoints defined in
api-contract.yaml, ensuring API parity. - Client-side utilities
resolveApiPathanddesktopAwareFetchtransparently route requests to the appropriate backend without component-level changes. - Platform bridges abstract environment-specific capabilities while preserving universal business logic.
Frequently Asked Questions
How does Routa maintain API consistency between Next.js and Tauri?
Routa enforces consistency through the api-contract.yaml OpenAPI specification, which serves as the shared boundary for both backends. Both the Next.js handlers in src/app/api/ and the Axum handlers in crates/routa-server/src/api/ implement the exact same endpoints, ensuring that a board created on the web opens identically in the desktop client without data loss.
Why does the desktop backend use Axum instead of bundling Node.js?
The desktop runtime uses Axum—a high-performance Rust HTTP server—to avoid bundling the entire Node.js runtime with the application binary. This approach reduces the distribution size significantly while enabling native Rust capabilities for local-first file-system operations through Tauri's bridge architecture.
How does the client detect which backend to call at runtime?
The client relies on the isDesktop environment check imported in src/client/config/backend.ts. When desktopAwareFetch detects the Tauri environment, it automatically rewrites relative URLs to point to http://127.0.0.1:3210, while web deployments use relative /api paths resolved by the Next.js dev server or production host.
Can multi-agent protocols function on both web and desktop platforms?
Yes, agents utilizing protocols like ACP, MCP, and A2A operate interchangeably across both surfaces because they communicate through the standardized REST/SSE endpoints defined in api-contract.yaml. Since the API shape remains identical in both runtimes, agent integrations require no platform-specific modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →