TanStack Router exposes router lifecycle events through router.subscribe. This is useful for imperative side effects like analytics, resetting external state, or running DOM-dependent logic after navigation.
router.subscribe takes an event name and a listener, then returns an unsubscribe function:
const unsubscribe = router.subscribe('onResolved', (event) => {
console.info('Navigation finished:', event.toLocation.href)
})
// Later, clean up the listener
unsubscribe()
router.subscribe is best for imperative integrations that need to observe navigation without driving rendering:
If you need reactive UI updates, prefer framework hooks like useRouterState, useSearch, and useParams instead of subscribing manually.
TanStack Router emits these lifecycle events:
For the full event payload types, see the RouterEvents type.
For a normal navigation, the events usually flow like this:
You usually do not need every event. A good rule of thumb is:
Navigation events receive location change metadata describing what changed:
const unsubscribe = router.subscribe('onBeforeNavigate', (event) => {
console.info({
from: event.fromLocation?.href,
to: event.toLocation.href,
pathChanged: event.pathChanged,
hrefChanged: event.hrefChanged,
hashChanged: event.hashChanged,
})
})
A few useful details:
onResolved is a good default for analytics because it fires after navigation finishes:
const unsubscribe = router.subscribe('onResolved', ({ toLocation }) => {
analytics.track('page_view', {
path: toLocation.pathname,
href: toLocation.href,
})
})
If you use a mutation library without keyed mutation state, clear it after navigation:
const unsubscribe = router.subscribe('onResolved', ({ pathChanged }) => {
if (pathChanged) {
mutationCache.clear()
}
})
Use onRendered when your side effect depends on the new route content already being in the DOM:
const unsubscribe = router.subscribe('onRendered', ({ toLocation }) => {
focusPageHeading(toLocation.pathname)
})
If you subscribe from a component or framework effect, always return the unsubscribe function from your cleanup so the listener is removed when the component unmounts.