TanStack Router DevTools — the router’s X-ray

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Start with router_fundamentals.html — understand the fundamentals first (route tree → history → matching → render). DevTools only visualizes what happens underneath, so the panel makes no sense until you know the pipeline it is mirroring.

one panel, the whole router

Drop <TanStackRouterDevtools router={router}/> once (dev-only) and you get a floating panel that mirrors the router's live state. Four tabs cover the full surface: Route Tree (parent→child, eager vs lazy, matched chain highlighted), Match Inspector (params, validated search, context, loader status), Navigation History (timeline + preload hits/misses + redirect chains), Performance (match time, loader time, render time). It is read-only — it subscribes to router.state and never mutates it.

tabshowsanswers the question
Route Tree full tree, eager/lazy badges, matched chain highlighted "which routes exist, and which ones are live right now?"
Match pathname, params, validated search, context, loader status "what did the router extract from THIS url?"
History past navigations, preload hit/miss, redirect chains "how did the user get here, and was anything prefetched?"
Performance match time, loader exec time, component render time "where is this navigation spending time?"

1 · the DevTools panel (edit me)

This is a faithful simulation of the four-tab panel. In a real app the data comes from router.state subscriptions; here it is a static routerState snapshot so you can see exactly what each tab renders. Edit and hit "compile & render".

2 · Babel compiles JSX → element tree

<DevToolsPanel/> becomes React.createElement(DevToolsPanel). Each setActiveTab(id) re-renders only the panel; the simulated routerState is static so the data the tabs show is stable.

// (hit "compile & render" to see Babel's output)

3 · live React (the panel, proven)

The gold-check clicks through every tab automatically: asserts the panel rendered, the Route Tree shows 4 routes, the Match tab shows params id=42 + search tab=posts, the History tab shows ≥2 entries, and the Performance tab shows match + loader times.

active tab: · gold: routes(4) → match(id=42, tab=posts) → history(≥2) → perf(match+loader)

intent → tab / pattern

intentpatternwhy
mount DevTools <TanStackRouterDevtools router={router} position="bottom-right"/> one mount, dev-only; subscribes to router.state
hide in prod process.env.NODE_ENV === 'development' && <Devtools/> the package is dev-only; never ship it to users
position the logo position="top-left|top-right|bottom-left|bottom-right" defaults to bottom-left; the trigger logo floats over the app
"why did this route match?" Route Tree tab → matched-chain highlight shows eager vs lazy + the ancestor chain the URL activated
"what params/search did it extract?" Match tab → params / search rows validated search = post-validateSearch values, not raw query string
"did the preload fire?" History tab → preload hit/miss badge correlates intent-hover/preload with the actual navigation
"why is navigation slow?" Performance tab → match / loader / render times splits time across the three pipeline stages