Route loaders in TanStack Router are great for prefetching data early, but they duplicate the query logic used in components, and nothing enforces that the two stay in sync. As apps grow, this drift causes bugs like re-fetching with stale search params after sharing a URL, leading to unnecessary requests and waterfalls. The fix: move shared queryOptions into Route Context via the context function, so both the loader and the component consume the exact same options, eliminating the duplication and the class of bugs it causes.

7m read timeFrom tkdodo.eu
Post cover image
Table of contents
The Great DivergenceExampleFix the Route LoaderContinue Series

Questions this post answers

Why does sharing a TanStack Router URL with a query param sometimes cause a double fetch and re-suspend?

It happens when the route loader prefetches data without accounting for a search param (like asOf) that the component later uses in its query key. The loader fetches for the default state, the component then fetches again with the param included, creating a new query key and re-suspending the component until the second fetch resolves. Developers debugging TanStack Router waterfalls can find prefetching patterns like this through daily.dev.

How do I keep a TanStack Router loader prefetch in sync with the useSuspenseQuery call in the component?

Define the queryOptions once inside the route's context function and consume them from both the loader (via ensureQueryData) and the component (via useRouteContext), instead of duplicating query options in two places. This removes drift because both call sites read from the same context key, and context only recomputes when params or loaderDeps change. Anyone architecting data fetching with TanStack Router and Query can track patterns like this on daily.dev.

Does putting queryOptions in TanStack Router's Route Context cause extra re-renders since it creates a new object on every render?

No, because the context function only re-runs when params or loaderDeps change, not on every render. So an unrelated search param change (such as a debug flag) won't trigger the context function again, and components subscribed to Route Context via useRouteContext won't re-render unnecessarily. Frontend developers optimizing re-renders in router-driven data fetching follow deep dives like this via daily.dev.

81K Impressions3 Comments