How to Debug Tauri Desktop App Routes and Backend Fallback Mapping in Routa

To debug Tauri desktop app routes and backend fallback mapping in Routa, verify that the embedded Rust/Axum server logs show the SPA fallback resolution, confirm the static_dir path points to the Next.js static export containing __placeholder__ files, and test that workspace IDs are correctly rewritten when serving HTML responses.

Routa utilizes a dual-backend architecture where a Next.js static export runs alongside an embedded Rust HTTP server inside a Tauri desktop container. When you debug Tauri desktop app routes and backend fallback mapping in Routa, you are essentially tracing how the Rust server intercepts requests and rewrites placeholder files to serve dynamic content, since Tauri's native protocol lacks built-in SPA fallback support.

Architecture Overview: Why SPA Fallback is Necessary

Unlike web deployments, the Tauri desktop shell does not provide native SPA fallback handling for client-side routing. Instead, Routa delegates this responsibility to an embedded Axum server that starts alongside the desktop application.

The fallback flow operates as follows:

  1. Tauri launches the Rust server via start_server (defined in crates/routa-server/src/lib.rs).
  2. The server loads the static_dir (typically out/desktop from the Next.js export) and constructs an Axum router.
  3. When a request misses API routes, the fallback_service triggers.
  4. The resolve_static_target function maps URLs to placeholder files (e.g., /workspace/default/kanban → workspace/__placeholder__/kanban.html).
  5. If the URL contains a concrete workspace ID, the server rewrites the __placeholder__ token to the actual ID before returning the response.
  6. For React Server Components (RSC), the server sets the content-type to text/x-component; charset=utf-8.

Step-by-Step Debugging Guide

1. Check Server Startup Logs

Verify the server initialized the fallback service correctly. In crates/routa-server/src/lib.rs (lines 326-332), the server logs the static directory path and fallback configuration on startup.

Look for log entries resembling:

SPA fallback: /workspace/default -> /path/to/out/desktop/workspace/__placeholder__.html (rsc=false)

If these logs are absent, the server may not have received the static_dir configuration.

2. Validate the static_dir Configuration

In apps/desktop/src-tauri/src/main.rs, inspect the server initialization around line 1156. Ensure the ServerConfig struct includes the static_dir field pointing to the Next.js export:

let config = ServerConfig {
    host: "127.0.0.1".into(),
    port: 3210,
    db_path: "routa.db".into(),
    static_dir: Some("out/desktop".into()), // Critical for SPA fallback
};

let _addr = routa_server::start_server(config).await?;

If static_dir is None, the fallback service is disabled and all non-file requests will return 404.

3. Inspect Placeholder Files

Verify that the Next.js static export generated the expected placeholder files. The server expects to find files like:

If these files are missing, rerun the Next.js static export (npm run build && npm run export) before building the Tauri app.

4. Reproduce Requests Manually

Test the fallback logic directly using curl against the local server port (default 3210):

curl http://127.0.0.1:3210/workspace/your-workspace-id/kanban

Examine the response HTML. It should contain your actual workspace ID, not the string __placeholder__. If you receive a 404, the placeholder file is likely missing or the static_dir path is incorrect.

5. Check the Rewrite Logic

If the placeholder file is served but the workspace ID is not injected, inspect the fallback_service implementation in crates/routa-server/src/lib.rs (lines 345-355). The code extracts workspace_segments and conditionally rewrites content based on should_rewrite_workspace_placeholder.

Add temporary debug logging to verify the extracted ID:

tracing::debug!("Rewriting placeholder with actual workspace ID: {}", actual_workspace_id);

Note that rewrite is skipped for session-specific routes where the placeholder is already per-session.

6. Run the E2E Regression Suite

Routa includes end-to-end tests specifically for desktop routing. Execute:

pnpm test:e2e e2e/tauri-backend-check.spec.ts

This test validates that all SPA routes return HTML containing the correct workspace ID, serving as the official regression guard for the fallback mapping.

7. Verify Client-Side Request Routing

Ensure the frontend is actually calling the Rust server and not attempting to load files via Tauri's asset protocol. All frontend code should use desktopAwareFetch from src/client/utils/diagnostics.ts (lines 143-156):

export function desktopAwareFetch(input: RequestInfo, init?: RequestInit) {
  const url = typeof input === "string" ? input : input.url;
  const full = isDesktop ? `http://127.0.0.1:3210${url}` : url;
  return fetch(full, init);
}

If isDesktop evaluates incorrectly or the port is wrong, requests will bypass the fallback logic entirely.

Common Pitfalls

  • Missing static export: Building the Tauri app without first generating out/desktop results in a server that cannot serve placeholders. Always export the Next.js static site before the desktop build.
  • Incorrect static_dir path: The path must be relative to the Tauri binary's working directory. Absolute paths or incorrect relative paths prevent the fallback service from locating placeholder files.
  • Workspace rewrite disabled: The should_rewrite_workspace_placeholder flag is false for session-specific routes. If debugging a session route, expect the placeholder to remain unchanged in the filename (although the content should still load).

Key Source Files

Summary

  • Routa handles SPA fallback through an embedded Rust/Axum server, not Tauri's native protocol.
  • Debug by checking server logs for "SPA fallback" entries in crates/routa-server/src/lib.rs.
  • Ensure static_dir in apps/desktop/src-tauri/src/main.rs points to the Next.js out/desktop export containing __placeholder__ files.
  • Use curl to test manual requests and verify workspace ID rewriting in the HTML response.
  • Validate the complete flow using e2e/tauri-backend-check.spec.ts.
  • Confirm frontend requests route through desktopAwareFetch to hit the local server at 127.0.0.1:3210.

Frequently Asked Questions

Why does my Tauri app show "Not found" for dynamic routes?

This occurs when the embedded Rust server cannot locate the placeholder files. Verify that static_dir is configured in apps/desktop/src-tauri/src/main.rs and that the out/desktop directory exists with the expected __placeholder__ subdirectory structure. Without these files, the fallback_service cannot generate a valid response.

How do I verify the placeholder rewrite is working correctly?

Send a manual HTTP request to http://127.0.0.1:3210/workspace/<your-id>/kanban using curl. Inspect the returned HTML for your specific workspace ID. If the response contains __placeholder__ instead of your ID, or if you receive a 404, the rewrite logic in crates/routa-server/src/lib.rs (lines 345-355) is not executing properly.

What is the purpose of the __placeholder__ token in Routa?

The __placeholder__ token allows the Next.js static export to generate generic HTML files for dynamic routes at build time. At runtime, the Rust server replaces this token with the actual workspace or session ID from the request URL, enabling a static export to serve dynamic content without server-side rendering.

Where can I find the E2E tests for desktop routing?

The end-to-end tests are located at e2e/tauri-backend-check.spec.ts in the repository root. These tests programmatically verify that all SPA routes return the correct HTML with proper workspace ID injection, providing an automated way to confirm the fallback mapping works after build changes.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →