Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Integrations
Guides

Router Events

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.

Basic usage

router.subscribe takes an event name and a listener, then returns an unsubscribe function:

tsx
const unsubscribe = router.subscribe('onResolved', (event) => {
  console.info('Navigation finished:', event.toLocation.href)
})

// Later, clean up the listener
unsubscribe()

When to use it

router.subscribe is best for imperative integrations that need to observe navigation without driving rendering:

  • Analytics and pageview tracking
  • Resetting external caches or mutation state
  • Logging navigation timing and transitions
  • Running DOM-dependent logic after routes render

If you need reactive UI updates, prefer framework hooks like useRouterState, useSearch, and useParams instead of subscribing manually.

Available events

TanStack Router emits these lifecycle events:

  • onBeforeNavigate - right before a navigation begins
  • onBeforeLoad - before route loading starts
  • onLoad - after the next location has committed and route matches have loaded
  • onBeforeRouteMount - after loading finishes, just before route components mount
  • onResolved - after the navigation has fully resolved
  • onRendered - after the route has rendered

For the full event payload types, see the RouterEvents type.

Typical event flow

For a normal navigation, the events usually flow like this:

  1. onBeforeNavigate
  2. onBeforeLoad
  3. onLoad
  4. onBeforeRouteMount
  5. onResolved
  6. onRendered

You usually do not need every event. A good rule of thumb is:

  • Use onBeforeNavigate or onBeforeLoad to observe navigation start
  • Use onResolved for analytics and cleanup after navigation finishes
  • Use onRendered for DOM-dependent work

Event payload

Navigation events receive location change metadata describing what changed:

tsx
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:

  • fromLocation can be undefined on the initial load
  • pathChanged tells you whether the pathname changed
  • hrefChanged includes pathname, search, and hash changes
  • hashChanged is useful for distinguishing hash-only navigations

Common patterns

Track pageviews

onResolved is a good default for analytics because it fires after navigation finishes:

tsx
const unsubscribe = router.subscribe('onResolved', ({ toLocation }) => {
  analytics.track('page_view', {
    path: toLocation.pathname,
    href: toLocation.href,
  })
})

Clear external mutation state

If you use a mutation library without keyed mutation state, clear it after navigation:

tsx
const unsubscribe = router.subscribe('onResolved', ({ pathChanged }) => {
  if (pathChanged) {
    mutationCache.clear()
  }
})

Run DOM-dependent logic

Use onRendered when your side effect depends on the new route content already being in the DOM:

tsx
const unsubscribe = router.subscribe('onRendered', ({ toLocation }) => {
  focusPageHeading(toLocation.pathname)
})

Unsubscribing in components

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.