A TanStack Router Navigation

mirzap1 pts0 comments

Inside a TanStack Router Navigation | TanStack Blog

Search

Ask AI<br>Log In

BlogOn this page

Inside a TanStack Router Navigation<br>*]:border-0! [&>*+*]:border-l! [&>*+*]:border-border-default! bg-background-surface text-text-primary shadow-sm [&>[aria-pressed=true]]:bg-text-primary [&>[aria-pressed=true]]:text-background-default [&>[aria-pressed=true]]:shadow-sm">Copy page

by Florian Pellet on Aug 14, 2026.

From application code, a navigation looks almost too simple:<br>ts

await router.navigate({ to: '/account' })

That simplicity gets deceptive as soon as a few things overlap.

Hovering /account starts preloading it.<br>Clicking /account reuses the loading work that the hover started.<br>Before it finishes, the user clicks /settings, whose route loaders run in parallel.<br>One /settings loader errors while another redirects to /login.<br>The earlier /account work finishes and is cached, even though the user is no longer going there.<br>The router publishes /login, but its content suspends.<br>The framework eventually renders /login.<br>This is what the scenario looks like on a timeline. Don't worry about the labels yet, we'll unpack them as we go.

Account loading, the redirect to login, caching, and rendering proceed on different schedules.

Why can /account keep loading after the user moves on, but no longer update the page? Why does the /settings error never appear? And why isn't publishing /login the end of the navigation?<br>A navigation looks like one asynchronous operation, but the router must decide four things independently: which loading work should continue, which navigation may publish, what the route attempt decided, and whether the framework rendered it.<br>That distinction came out of rewriting TanStack Router's loading system. We had dozens of bugs across preloading, redirects, caching, pending UI, SSR, and more. They looked unrelated, but many had the same root cause: the router compressed too many facts into too few variables. Cancellation, supersession, publication, and rendering were allowed to stand in for each other.1<br>Application code still gets one navigate() call. Inside the router, separate lifetimes keep track of those four answers.2<br>Start With One Successful Navigation#<br>Before we return to all that overlap, it helps to focus on one ordinary navigation. There is no second click, no error, and no redirect here. The route simply loads slowly enough to show pending UI, then succeeds.<br>Each row follows a different part of the navigation, and the arrows between them are handoffs. Loaders can run while a pending timer races them. The framework can render pending UI while the private route branch keeps loading. We'll decode the internal labels just below.

One successful navigation still moves along several schedules: loaders run, pending UI may appear, final matches publish, and the framework handles each publication in its own time.

The lane in the middle is a private, unpublished draft of the matched route branch. Following it across the diagram:<br>Route matching turns the destination URL into an ordered branch of route matches.<br>The navigation becomes the current transaction . This is what grants it permission to publish if it is still current when the work finishes.3<br>The private lane builds route context and runs beforeLoad from parent to child. Each child needs its parent's completed context, so this part is intentionally serial.<br>Eligible route loaders can then run in parallel. Each actual loader invocation is a loader flight .<br>If the loaders take longer than pendingMs, the router can publish pending UI. The framework handles that publication while the lane keeps working toward the final result.<br>Once the loader outcomes are ready, the lane selects the successful result.<br>This navigation is still current, so it publishes the final matches. The framework acknowledges that publication, and the navigation can finish.<br>Note<br>The diagram's release flight label marks where the lane releases its temporary claim. That does not necessarily end ownership of settled data. The loader-flight section below explains why.

Nothing goes wrong in this version. The transaction is never replaced, the loaders do not error or redirect, and both publications render successfully. Now change one assumption at a time:<br>What if...The answer belongs toanother consumer needs a loader that is already running?A loader flight and its leasesthe user starts another navigation before this one finishes?The current transaction parallel loaders produce different kinds of outcomes?A private lane another publication takes over before the framework renders this one?A framework render receipt<br>Usually, all four answers arrive within a few milliseconds of each other, preserving the illusion of one asynchronous task, but it is also plenty of time for another event to change what should happen next.<br>When Navigations Overlap#<br>The opening scenario puts all four complications back together. Here is the whole thing at once, and then one section per...

navigation router route framework loaders loader

Related Articles