How to Handle Routing with Agent-Native: A Complete Guide to React Router Integration

Agent-Native builds its UI on top of React Router and adds a thin abstraction layer that makes routing work seamlessly across the client, the server, and the agent runtime.

Agent-Native templates unify standard React Router patterns with agent-aware navigation state, enabling both user interactions and background agent processes to control the application view. This architecture requires understanding three core components: static route definitions using flatRoutes, server-side request handling through Vite virtual modules, and the NavigationState bridge that keeps the UI and agent synchronized.

Static Route Definition with flatRoutes

Agent-Native defines its route tree using React Router's file-system routing conventions. In templates/dispatch/app/routes.ts, you export a RouteConfig array generated by the flatRoutes helper.

// templates/dispatch/app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export const routes: RouteConfig[] = flatRoutes([
  { path: "/", element: <Overview /> },
  { path: "chat", element: <Chat /> },
  { path: "metrics", element: <Metrics /> },
]);

This configuration creates the baseline route tree that both the client and server use during rendering.

Server-Side Rendering Entry Point

For SSR, Agent-Native uses a dedicated server entry point that imports the virtual React Router build. The templates/dispatch/ssr-entry.ts file creates a request handler using React Router's createRequestHandler and loads routes from the virtual module virtual:react-router/server-build.

// templates/dispatch/ssr-entry.ts
import { createRequestHandler } from "react-router";
import { defineServer } from "@builder.io/react";

export default defineServer({
  async handler() {
    return createRequestHandler({
      routes: () => import("virtual:react-router/server-build"),
    });
  },
});

This setup ensures server-rendered HTML matches the client-side route hierarchy exactly.

Synchronizing Navigation State Between UI and Agent

The useNavigationState hook in templates/dispatch/app/hooks/use-navigation-state.ts forms the bridge between the browser URL and the agent runtime. It uses useAgentRouteState from packages/core/src/client/router.ts to register callbacks that translate between browser locations and structured NavigationState objects.

// templates/dispatch/app/hooks/use-navigation-state.ts
import { useLocation } from "react-router";
import { appBasePath, markAgentChatHomeHandoff, useAgentRouteState } from "@agent-native/core/client";

export function useNavigationState() {
  const location = useLocation();

  useAgentRouteState({
    getNavigationState: ({ pathname, search }) => {
      const cleanPath = routerPath(pathname);
      return buildDispatchNavigationState(cleanPath, search);
    },

    getCommandPath: (cmd) => {
      const path = cmd.path ?? resolvePath(cmd.view) ?? "/overview";
      return routerPath(path);
    },

    onNavigate: (cmd, path) => {
      if (routerPath(location.pathname) === "/chat" && pathnameFromPath(path) !== "/chat") {
        markAgentChatHomeHandoff("dispatch");
      }
    },
  });
}

The hook implements three critical functions:

  • getNavigationState: Converts the current browser URL into a structured state object (view, extension, dream, etc.) that the agent can consume.
  • getCommandPath: Resolves agent navigation commands into valid browser paths using resolvePath.
  • onNavigate: Handles side effects when navigation occurs, such as marking handoff points between chat contexts.

Handling Multi-App Workspace Paths

Agent-Native supports workspaces hosting multiple independent apps (e.g., /dispatch, /calendar). The appBasePath() and appPath() helpers in packages/core/src/workspace-files/tool.ts automatically manage workspace-level base paths, ensuring routes remain portable.

The routerPath() function normalizes URLs by stripping the basename repeatedly to prevent double-prefix bugs:

function routerPath(path: string): string {
  const basePath = appBasePath(); // e.g., "/dispatch"
  if (!basePath) return path;
  let result = path;
  // Strip the basename up to 4 times to protect against double-prefix bugs
  for (let i = 0; i < 4; i++) {
    if (result === basePath) return "/";
    if (!result.startsWith(`${basePath}/`)) break;
    result = result.slice(basePath.length) || "/";
  }
  return result;
}

This normalization ensures the UI router and agent runtime agree on the current local pathname regardless of the app's mount point in the workspace.

Triggering Navigation from Agent Actions

Agents navigate the UI by invoking the navigate action with a view name or path. The system uses resolveView and resolvePath (implemented in templates/dispatch/app/hooks/use-navigation-state.ts lines 146-226) to map between semantic view names and actual URL paths.

import { defineAction } from "@agent-native/core";

export default defineAction({
  name: "openMetrics",
  description: "Navigate the UI to the Metrics page",
  run: async ({ agent }) => {
    await agent.navigate({ view: "metrics" });
  },
});

When the agent calls navigate, the getCommandPath callback resolves the view to a URL, routerPath normalizes it, and React Router performs the navigation.

Summary

  • Agent-Native routing extends React Router with agent-aware abstractions that work across client, server, and runtime environments.
  • Static routes are defined in templates/dispatch/app/routes.ts using flatRoutes and RouteConfig from React Router's dev tools.
  • Server rendering uses createRequestHandler in templates/dispatch/ssr-entry.ts to serve the virtual React Router build.
  • Navigation state is synchronized through useNavigationState, which bridges browser URLs and agent commands via useAgentRouteState from packages/core/src/client/router.ts.
  • Path normalization handles multi-app workspaces through appBasePath() and routerPath() to prevent routing errors when apps are mounted at sub-paths.

Frequently Asked Questions

How does Agent-Native handle base URL paths in multi-app workspaces?

Agent-Native uses the appBasePath() and appPath() helpers found in packages/core/src/workspace-files/tool.ts to automatically prepend or strip the workspace-level base path. The routerPath() function in templates/dispatch/app/hooks/use-navigation-state.ts strips the basename up to four times to guard against double-prefix bugs, ensuring each app can be developed in isolation and mounted at any sub-path without hardcoding.

What is the difference between routerPath and appBasePath?

appBasePath() returns the workspace-level base URL (e.g., /dispatch) that prefixes all routes for the current app, while routerPath() is a normalization function that removes this base path from a URL to produce a clean local pathname. You use appBasePath() when constructing URLs to ensure they include the mount point, and routerPath() when parsing incoming URLs to determine the local route.

How do agent commands trigger navigation in the UI?

When an agent calls agent.navigate({ view: "metrics" }), the useAgentRouteState hook (exposed from packages/core/src/client/router.ts) invokes its getCommandPath callback. This callback uses resolvePath to map the view name to a URL, normalizes it with routerPath(), and returns the result to React Router's navigate function, triggering the actual browser navigation.

Can I use standard React Router hooks alongside Agent-Native's routing?

Yes. Agent-Native is built directly on React Router, so hooks like useLocation, useNavigate, and useParams work normally. The useNavigationState hook internally uses useLocation to react to URL changes, meaning you can use standard hooks for UI-specific logic while reserving Agent-Native's useAgentRouteState for agent synchronization and workspace-aware path handling.

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 →