Navigated to /docs/core/navigation-and-route-state

Navigation and route state

Navigate, prefetch, inspect route resolution, and own route subscriptions without leaking browser listeners.

Read route status as a state machine

TS
tsimport {
  getRouteStatus,
  subscribeRouteStatus,
} from "@tavojs/core/router";

console.debug(getRouteStatus("/reports").status);

const stop = subscribeRouteStatus((status) => {
  document.documentElement.dataset.routeStatus = status.status;
}, "/reports");

// Return stop from the lifecycle owner.
StatusMeaning
idleNo active or reusable resolution is recorded for the path.
loadingAn active navigation is running middleware and loaders.
prefetchingBackground resolution is running without changing history.
readyA resolved result is available for the path.
redirectingMiddleware or route resolution selected another location.
errorResolution failed; inspect the status error for reporting.

Each record follows RouteStatus. Read a path with getRouteStatus(pathname) and observe future transitions with subscribeRouteStatus(listener, pathname?).

  • getCurrentPathname() reads the current browser path.

  • getAvailableRoutes() returns discovered PageRouteDefinition records; the catalog is not an authorization boundary.

  • getResolvedRoute(pathname) returns the current resolved route snapshot when available. Pass the path explicitly when inspecting anything other than the active route. The renderer-state shape is intentionally not a separately importable route-author contract.

Read once, subscribe, and return cleanup

subscribePathname, subscribeAvailableRoutes, and subscribeRouteStatus publish future changes; they do not replace the initial synchronous read. A createTavo controller is a natural owner because onMount can return one cleanup that releases every subscription.

TSX
tsximport {
  createTavo,
  TavoController
} from "@tavojs/core";
import {
  getCurrentPathname,
  getRouteStatus,
  subscribePathname,
  subscribeRouteStatus,
  type RouteStatus
} from "@tavojs/core/router";
import { Text } from "@tavojs/ui";

type RouteProgressState = {
  pathname: string;
  status: RouteStatus["status"];
};

class RouteProgressController extends TavoController {
  sync(pathname = getCurrentPathname()) {
    this.model.patch({
      pathname,
      status: getRouteStatus(pathname).status
    });
  }

  onMount() {
    this.sync();
    const stopPathname = subscribePathname((pathname) => {
      this.sync(pathname);
    });
    const stopStatus = subscribeRouteStatus((status) => {
      this.model.patch({
        pathname: status.pathname,
        status: status.status
      });
    });

    return function cleanupRouteProgress() {
      stopPathname();
      stopStatus();
    };
  }
}

export const RouteProgress = createTavo<
  Record<string, never>,
  RouteProgressState,
  RouteProgressController
>({
  model: () => ({
    pathname: "/",
    status: "idle"
  }),
  controller: RouteProgressController,
  view: ({ state }) => {
    return (
      <Text aria-live="polite">
        {state.pathname}: {state.status}
      </Text>
    );
  }
});

Prefetch without navigating

TSX
tsximport { Link, prefetchRoute } from "@tavojs/core/router";

export function ProjectLink({ id }: { id: string }) {
  const pathname = `/projects/${id}`;
  let prefetch: AbortController | null = null;

  function startPrefetch() {
    prefetch?.abort();
    prefetch = new AbortController();
    void prefetchRoute(pathname, {
      signal: prefetch.signal
    });
  }

  function stopPrefetch() {
    prefetch?.abort();
    prefetch = null;
  }

  return (
    <span
      onMouseEnter={startPrefetch}
      onMouseLeave={stopPrefetch}
      onFocus={startPrefetch}
      onBlur={stopPrefetch}
    >
      <Link to={pathname}>
        Open project {id}
      </Link>
    </span>
  );
}
  • prefetchRoute runs the target's client-eligible middleware and loaders but does not change history or render the target pending component.

  • Status moves through prefetching and then ready or error. Cancellation returns the prefetch to idle.

  • Pass an AbortSignal when hover, focus, a controller, or another owner can stop needing the result.

  • Prefetch is an optimization. Navigation must still work when no prefetch ran, failed, or was evicted.

Verify navigation and cancellation

  • Replace a slow navigation with a second destination and confirm the obsolete result never becomes active.

  • Abort a hover prefetch and confirm it neither changes the URL nor displays pending UI.

  • Mount and unmount subscription-owning components repeatedly while checking that each cleanup runs.

  • Test Link with keyboard activation, modifier keys, downloads, external URLs, hashes, and browser back/forward.

BASH
bashnpx tavo routes
npx tavo inspect route /projects/example --json

Look up exact public types

Follow linked API names to their canonical TypeScript declarations and package boundaries.