This guide covers debugging common TanStack Router problems, from route matching failures to navigation issues and performance problems.
Use TanStack Router DevTools for real-time debugging, add strategic console logging, and follow systematic troubleshooting patterns to identify and resolve router issues quickly.
Install and configure the DevTools for the best debugging experience:
npm install @tanstack/router-devtools// src/App.tsx
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
function App() {
return (
<div>
<RouterProvider router={router} />
{/* Only shows in development */}
<TanStackRouterDevtools router={router} />
</div>
)
}DevTools Features:
Route Tree Visualization - See your entire route structure
Current Route State - Inspect active route data, params, and search
Navigation History - Track navigation events and timing
Route Matching - See which routes match current URL
Performance Metrics - Monitor route load times and re-renders
Enable debug mode for detailed console logging:
const router = createRouter({
routeTree,
defaultPreload: 'intent',
context: {
// your context
},
// Enable debug mode
debug: true,
})Add router to global scope for debugging:
// In development only
if (import.meta.env.DEV) {
window.router = router
}
// Console debugging commands:
// router.state - current router state
// router.navigate() - programmatic navigation
// router.history - navigation historySymptoms:
Route exists but shows 404 or "Not Found"
Console shows route matching failures
Debugging Steps:
Check Route Path Definition
// ❌ Common mistake - missing leading slash
const route = createRoute({
path: 'about', // Should be '/about'
// ...
})
// ✅ Correct
const route = createRoute({
path: '/about',
// ...
})Verify Route Tree Structure
// Debug route tree in console
console.log('Route tree:', router.routeTree)
console.log('All routes:', router.routesById)Check Parent Route Configuration
// Ensure parent route is properly defined
const childRoute = createRoute({
getParentRoute: () => parentRoute, // Must return correct parent
path: '/child',
// ...
})Symptoms:
useParams() returns undefined or wrong values
Route params not being parsed correctly
Debugging Steps:
Verify Parameter Syntax
// ❌ Wrong parameter syntax
path: '/users/{id}' // Should use $
// ✅ Correct parameter syntax
path: '/users/$userId'Check Parameter Parsing
const route = createRoute({
path: '/users/$userId',
// Add parameter validation/parsing
params: {
parse: (params) => ({
userId: Number(params.userId), // Convert to number
}),
stringify: (params) => ({
userId: String(params.userId), // Convert back to string
}),
},
component: () => {
const { userId } = Route.useParams()
console.log('User ID:', userId, typeof userId) // Debug output
return <div>User {userId}</div>
},
})Debug Current URL and Params
function DebugParams() {
const location = useLocation()
const params = Route.useParams()
console.log('Current pathname:', location.pathname)
console.log('Parsed params:', params)
return null // Just for debugging
}Symptoms:
Links don't navigate
Programmatic navigation fails silently
Browser URL doesn't update
Debugging Steps:
Check Link Configuration
// ❌ Common mistakes
<Link to="about">About</Link> // Missing leading slash
<Link href="/about">About</Link> // Wrong prop (href instead of to)
// ✅ Correct
<Link to="/about">About</Link>Debug Navigation Calls
function NavigationDebug() {
const navigate = useNavigate()
const handleNavigate = () => {
console.log('Attempting navigation...')
navigate({
to: '/dashboard',
search: { tab: 'settings' },
})
.then(() => console.log('Navigation successful'))
.catch((err) => console.error('Navigation failed:', err))
}
return <button onClick={handleNavigate}>Navigate</button>
}Check Router Context
// Ensure component is inside RouterProvider
function ComponentWithNavigation() {
const router = useRouter() // Will throw error if outside provider
console.log('Router state:', router.state)
return <div>...</div>
}Symptoms:
Navigating to one route but ending up somewhere else
Infinite redirect loops
Debugging Steps:
Check Route Guards
const route = createRoute({
path: '/dashboard',
beforeLoad: ({ context, location }) => {
console.log('Before load - location:', location.pathname)
console.log('Auth state:', context.auth)
if (!context.auth.isAuthenticated) {
console.log('Redirecting to login...')
throw redirect({ to: '/login' })
}
},
// ...
})Debug Redirect Chains
// Add to router configuration
const router = createRouter({
routeTree,
context: {
/* ... */
},
// Log all navigation events
onNavigate: ({ location, type }) => {
console.log(`Navigation (${type}):`, location.pathname)
},
})Symptoms:
useLoaderData() returns undefined
Loading states not working correctly
Data not refreshing
Debugging Steps:
Check Loader Implementation
const route = createRoute({
path: '/posts',
loader: async ({ params, context }) => {
console.log('Loader called with params:', params)
try {
const data = await fetchPosts()
console.log('Loader data:', data)
return data
} catch (error) {
console.error('Loader error:', error)
throw error
}
},
component: () => {
const data = Route.useLoaderData()
console.log('Component data:', data)
return <div>{/* render data */}</div>
},
})Debug Loading States
function DataLoadingDebug() {
const location = useLocation()
console.log('Route status:', {
isLoading: location.isLoading,
isTransitioning: location.isTransitioning,
})
return null
}Check Loader Dependencies
const route = createRoute({
path: '/posts/$postId',
loader: async ({ params }) => {
// Loader will re-run when params change
console.log('Loading post:', params.postId)
return fetchPost(params.postId)
},
// Add dependencies for explicit re-loading
loaderDeps: ({ search }) => ({
refresh: search.refresh,
}),
})Symptoms:
URL search params don't update
useSearch() returns stale data
Search validation errors
Debugging Steps:
Check Search Validation Schema
const route = createRoute({
path: '/search',
validateSearch: (search) => {
console.log('Raw search params:', search)
const validated = {
q: (search.q as string) || '',
page: Number(search.page) || 1,
}
console.log('Validated search params:', validated)
return validated
},
component: () => {
const search = Route.useSearch()
console.log('Component search:', search)
return <div>Query: {search.q}</div>
},
})Debug Search Navigation
function SearchDebug() {
const navigate = useNavigate()
const currentSearch = Route.useSearch()
const updateSearch = (newSearch: any) => {
console.log('Current search:', currentSearch)
console.log('New search:', newSearch)
navigate({
to: '.',
search: (prev) => {
const updated = { ...prev, ...newSearch }
console.log('Final search:', updated)
return updated
},
})
}
return (
<button onClick={() => updateSearch({ q: 'test' })}>Update Search</button>
)
}Symptoms:
Components re-rendering too often
Performance lag during navigation
Memory usage increasing
Debugging Steps:
Use React DevTools Profiler
// Wrap your app for profiling
import { Profiler } from 'react'
function App() {
return (
<Profiler
id="Router"
onRender={(id, phase, actualDuration) => {
console.log(`${id} ${phase} took ${actualDuration}ms`)
}}
>
<RouterProvider router={router} />
</Profiler>
)
}Optimize Route Subscriptions
// ❌ Subscribes to all search params
function MyComponent() {
const search = Route.useSearch()
return <div>{search.someSpecificField}</div>
}
// ✅ Subscribe only to specific field
function MyComponent() {
const someSpecificField = Route.useSearch({
select: (search) => search.someSpecificField,
})
return <div>{someSpecificField}</div>
}Monitor Route State Changes
// Add to router configuration
const router = createRouter({
routeTree,
context: {
/* ... */
},
onUpdate: (router) => {
console.log('Router state updated:', {
pathname: router.state.location.pathname,
isLoading: router.state.isLoading,
matches: router.state.matches.length,
})
},
})Symptoms:
Memory usage constantly increasing
Browser becomes slow over time
Route components not cleaning up
Debugging Steps:
Check Component Cleanup
function MyComponent() {
const [data, setData] = useState(null)
useEffect(() => {
const subscription = someService.subscribe(setData)
// ✅ Always clean up subscriptions
return () => {
subscription.unsubscribe()
}
}, [])
return <div>{data}</div>
}Monitor Route Unmounting
function DebuggableComponent() {
useEffect(() => {
console.log('Component mounted')
return () => {
console.log('Component unmounted')
}
}, [])
return <div>Content</div>
}Symptoms:
TypeScript errors in route definitions
Type inference not working
Parameter types incorrect
Debugging Steps:
Check Route Tree Type Registration
// Ensure this declaration exists
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}Debug Route Type Generation
# Check if route types are being generated
ls src/routeTree.gen.ts
# Regenerate route types if needed
npx @tanstack/router-cli generateUse Type Assertions for Debugging
function TypeDebugComponent() {
const params = Route.useParams()
const search = Route.useSearch()
// Add type assertions to check what TypeScript infers
console.log('Params type:', params as any)
console.log('Search type:', search as any)
return null
}When debugging any router issue, start by collecting this information:
function RouterDebugInfo() {
const router = useRouter()
const location = useLocation()
useEffect(() => {
console.group('🐛 Router Debug Info')
console.log('Current pathname:', location.pathname)
console.log('Search params:', location.search)
console.log('Router state:', router.state)
console.log('Active matches:', router.state.matches)
console.log('Route tree:', router.routeTree)
console.groupEnd()
}, [location.pathname])
return null
}
// Add to your app during debugging
;<RouterDebugInfo />Create minimal reproduction:
// Minimal route for testing
const testRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/debug',
component: () => {
console.log('Test route rendered')
return <div>Debug Route</div>
},
})
// Add to route tree temporarily
const routeTree = rootRoute.addChildren([
// ... other routes
testRoute, // Add test route
])Verify basic setup - Router provider, route tree structure
Check route definitions - Paths, parent routes, configuration
Test navigation - Links, programmatic navigation
Validate data flow - Loaders, search params, context
Monitor performance - Re-renders, memory usage
// In browser console (when router is on window)
// Current router state
router.state
// Navigate programmatically
router.navigate({ to: '/some-path' })
// Get route by path
router.getRoute('/users/$userId')
// Check if route exists
router.buildLocation({ to: '/some-path' })
// View all registered routes
Object.keys(router.routesById)Monitor these requests when debugging:
Route code chunks - Check if lazy routes are loading
Loader data requests - Verify API calls from loaders
Failed requests - Look for 404s or failed API calls
Components Tab - Find router components and inspect props
Profiler Tab - Identify performance bottlenecks
Search for components - Find specific route components quickly
Check route path spelling and case sensitivity
Verify route is added to route tree
Ensure parent routes are properly configured
Component is likely outside RouterProvider
Route might not be properly registered
Check if using correct Route object
Check validateSearch schema
Verify search param types match schema
Look for required vs optional parameters
Usually caused by redirect in beforeLoad
Check for redirect loops
Verify authentication logic
const router = createRouter({
routeTree,
context: {
/* ... */
},
onUpdate: (router) => {
performance.mark('router-update')
},
onLoad: (router) => {
performance.mark('router-load')
performance.measure('router-load-time', 'router-update', 'router-load')
},
})const route = createRoute({
path: '/slow-route',
loader: async () => {
const start = performance.now()
const data = await fetchData()
const end = performance.now()
console.log(`Loader took ${end - start}ms`)
return data
},
})After debugging router issues, you might want to:
How to Set Up Testing - Add tests to prevent regressions
How to Deploy to Production - Ensure issues don't occur in production