Navigation and route state
Navigate, prefetch, inspect route resolution, and own route subscriptions without leaking browser listeners.
Read route status as a state machine
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.
| Status | Meaning |
|---|---|
idle | No active or reusable resolution is recorded for the path. |
loading | An active navigation is running middleware and loaders. |
prefetching | Background resolution is running without changing history. |
ready | A resolved result is available for the path. |
redirecting | Middleware or route resolution selected another location. |
error | Resolution 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 discoveredPageRouteDefinitionrecords; 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.
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
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>
);
}prefetchRouteruns 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
AbortSignalwhen 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
Linkwith keyboard activation, modifier keys, downloads, external URLs, hashes, and browser back/forward.
bashnpx tavo routes
npx tavo inspect route /projects/example --jsonLook up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.