# Routa's Dual-Backend Architecture: Next.js Web vs Tauri Desktop Explained

> Explore Routa's dual-backend architecture: Next.js web and Tauri desktop. Learn how unified domain logic ensures seamless API parity across platforms via a shared OpenAPI contract.

- Repository: [Fengda Huang/routa](https://github.com/phodal/routa)
- Tags: architecture
- Published: 2026-05-26

---

**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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/api-contract.yaml).

The Axum handlers—such as `list_workspaces` in [`crates/routa-server/src/api/workspaces.rs`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/src/client/config/backend.ts) detects the current environment and generates the appropriate base URL:

```typescript
// 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`](https://github.com/phodal/routa/blob/main/src/client/utils/diagnostics.ts) wraps standard fetch calls, automatically rewriting URLs to target the local Axum server when running inside the Tauri shell:

```typescript
// 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:

```tsx
// 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`](https://github.com/phodal/routa/blob/main/src/core/platform/web-bridge.ts) and [`src/core/platform/tauri-bridge.ts`](https://github.com/phodal/routa/blob/main/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:**
1. Browser calls `fetch('/api/workspaces')`
2. Next.js routes the request to `src/app/api/workspaces/`
3. TypeScript handler processes the request using in-memory models
4. Response returns through the Next.js API layer

**Desktop Execution Flow:**
1. Tauri UI calls `desktopAwareFetch('/api/workspaces')`
2. Helper detects desktop environment and rewrites URL to `http://127.0.0.1:3210/api/workspaces`
3. Request routes to the Axum handler in [`crates/routa-server/src/api/workspaces.rs`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/api/workspaces.rs)
4. Rust handler processes using the same domain models as the web side

```rust
// 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`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/lib.rs) | Axum server bootstrap and route initialization |
| [`api-contract.yaml`](https://github.com/phodal/routa/blob/main/api-contract.yaml) | Shared OpenAPI definition consumed by both backends |
| [`src/client/config/backend.ts`](https://github.com/phodal/routa/blob/main/src/client/config/backend.ts) | URL resolution logic for environment detection |
| [`src/client/utils/diagnostics.ts`](https://github.com/phodal/routa/blob/main/src/client/utils/diagnostics.ts) | Fetch wrapper that transparently routes to correct backend |
| [`src/core/platform/tauri-bridge.ts`](https://github.com/phodal/routa/blob/main/src/core/platform/tauri-bridge.ts) | Native API abstraction for desktop runtime |
| [`src/core/platform/web-bridge.ts`](https://github.com/phodal/routa/blob/main/src/core/platform/web-bridge.ts) | Browser API abstraction for web runtime |
| `src/core/models/` | Domain models shared across both runtimes |
| [`README.md`](https://github.com/phodal/routa/blob/main/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 on `127.0.0.1:3210` inside a Tauri shell.
- Both backends implement identical endpoints defined in [`api-contract.yaml`](https://github.com/phodal/routa/blob/main/api-contract.yaml), ensuring API parity.
- Client-side utilities `resolveApiPath` and `desktopAwareFetch` transparently 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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/api-contract.yaml). Since the API shape remains identical in both runtimes, agent integrations require no platform-specific modifications.