Debug Router Issues
Purpose and Scope
Debugging a TanStack Router application usually means confirming that the route tree, the current URL, the matched route branch, and the navigation APIs all agree. Router is designed around a typed route contract, so many problems surface as either type errors during development or unexpected runtime state during navigation. This page gives a practical workflow for moving from symptoms to evidence: first inspect the live router with Devtools, then compare what the UI shows against route files, links, params, search validation, loaders, and redirects. Sources: docs/router/devtools.md
The dedicated Router Devtools are the main runtime inspection aid. The docs describe them as a separate package intended to visualize the inner workings of TanStack Router and save debugging time when an application is in a pinch. That framing matters: Devtools are not only a convenience panel after everything works; they are the fastest way to confirm which router instance is active, whether the current location is matching the route you expect, and whether a navigation issue is caused by route configuration, stale generated output, or component placement. Sources: docs/router/devtools.md
Relevant Source Files
- docs/router/devtools.md — Documents the Router Devtools packages, framework imports, root-route usage, manual router instance wiring, floating mode behavior, and the production import variant used when debugging outside normal development mode.
Core Debugging Primitives
Use three primitives when investigating a Router problem. The first is the route tree: the application contract that determines legal paths, params, search state, loaders, and navigation targets. The second is the router instance: the live object passed to the framework provider and optionally to Devtools. The third is the rendered route branch: the matched parent and child routes currently producing UI through outlets. When these three disagree, symptoms can look unrelated. A link may autocomplete correctly but navigate to the wrong screen, a loader may not run, or a layout may remain mounted unexpectedly.
Devtools help connect those primitives because they can be rendered inside the route hierarchy or manually attached to the same router instance used by the provider. If the panel is mounted inside the root route, the documentation says it automatically connects to the router instance. If you place it elsewhere, pass the same router object through the devtools router prop. This distinction is important when debugging apps with custom shells, portals, micro-frontends, or multiple roots, because a devtools panel attached to the wrong instance can make the live route state look confusing. Sources: docs/router/devtools.md
Install and Wire Devtools
Start by adding the framework-specific Devtools package. The Router docs list separate packages for React and Solid, matching the framework router packages. Import the normal Devtools component during development and render it as high in the app as practical. The root route is usually the best place because it sits above child layouts and pages, so the inspector remains available while navigation changes the rest of the tree. Sources: docs/router/devtools.md
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'A minimal React root-route setup renders the outlet and the devtools side by side. This makes the panel part of the application route tree, so it can automatically locate the router context. The same pattern exists for Solid with the Solid router and Solid devtools package. If your root route already wraps providers, layouts, or error boundaries, keep Devtools near that root shell rather than inside a leaf page. That avoids losing the inspector when the very route you are debugging unmounts or fails to render. Sources: docs/router/devtools.md
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<Outlet />
<TanStackRouterDevtools />
</>
),
})Runtime Inspection Workflow
When route matching looks wrong, open Devtools and begin with the current location and matched route branch. Confirm that the URL path corresponds to the route you expected, then check whether a parent layout is matching before the child. Nested routing bugs often come from misunderstanding which parent route owns an outlet or where an index route should render. If the visible page does not match the URL, compare the runtime match stack with the route files or code-based route definitions and verify that the generated route map was refreshed after changes.
For navigation behavior, reproduce the action while Devtools is open. Click the link, run the imperative navigation, or trigger the redirect path, then watch whether the router state changes, stays pending, or lands on another branch. If the path changes but the component does not, inspect layout and outlet composition. If the route changes but data is stale, shift the investigation to loaders, cache freshness, invalidation, and loader dependencies. Devtools are especially useful here because they let you distinguish a matching problem from a data or rendering problem before rewriting route definitions.
For type issues, use the compiler error as the first clue and Devtools as the runtime confirmation. A typed link or navigate call should line up with the route tree’s known paths, params, and search schemas. If TypeScript says a path, param, or search value is invalid, check whether the route file name, route definition, or generated route tree reflects the shape you intended. If runtime behavior differs from the types, rebuild or regenerate the route tree and confirm the application imports the current generated output. This keeps debugging anchored to the route contract rather than isolated component assumptions.
Placement, Floating Mode, and Production Debugging
The documentation supports two placement strategies. Rendering Devtools inside the provider context is the easiest path because the component can automatically connect. Rendering outside that context is also supported by manually passing the same router instance that is passed to the Router provider. Use the manual approach for application shells where global UI is not a route child, or when you want the inspector mounted next to the provider while the route tree renders elsewhere. The important rule is consistency: the inspected router must be the router driving the user-visible navigation. Sources: docs/router/devtools.md
Floating mode is useful when you want an always-available inspector without permanently taking layout space. The docs explain that floating mode mounts Devtools as a fixed element and provides a corner toggle. The toggle state is stored in localStorage across reloads, so it is a good default for repeated debugging sessions. Place it as high in the app as possible for reliability, and consider starting closed with the documented initial open option when the panel should not distract during normal development. Sources: docs/router/devtools.md
function App() {
return (
<>
<RouterProvider router={router} />
<TanStackRouterDevtools initialIsOpen={false} />
</>
)
}Production debugging has a different import. The normal Devtools import is not shown when the environment is production. If you need to inspect an issue in an environment where the production flag is set, use the production variant documented for the framework package. Treat this as a targeted diagnostic tool rather than a default shipping dependency. It has the same options, so you can keep the same placement and router-instance strategy while making the panel available in the environment where the bug reproduces. Sources: docs/router/devtools.md
import { TanStackRouterDevtoolsInProd } from '@tanstack/react-router-devtools'Troubleshooting Checklist
- Install the matching framework package before debugging: React uses the React Router Devtools package, and Solid uses the Solid Router Devtools package.
- Render Devtools in the root route when possible so it can automatically connect to the router context.
- If Devtools are outside the provider, pass the exact router instance used by the application provider.
- For route matching issues, compare the live matched branch with the route tree and generated route output.
- For type issues, check that route file names, params, search validation, and generated types are up to date.
- For navigation issues, reproduce the link, navigate call, or redirect while observing whether router state changes or remains pending.
- For production-only issues, switch from the normal import to the documented production Devtools import.
Next Steps
After Devtools confirm where the mismatch occurs, move to the guide that matches the failing layer. Use the file-based routing and route generation pages when generated output or route files are suspect. Use the type-safety, path params, and search params pages when compiler feedback points to URL shape. Use the navigation, data loading, and error-boundary pages when runtime state changes but the user experience is wrong. Keep Devtools open while moving through those guides so every change is verified against the live router, not only against assumptions in source files.