Data Loading
Purpose and Scope
Data loading in TanStack Router is the route-centered workflow for fetching, preparing, caching, and reading data as part of navigation. A route loader is best understood as the data boundary attached to a route match: when the router prepares a match, it can execute that route’s loader, track pending state, cache the result, and expose the loaded value to route components and related hooks. This page explains that workflow from the reader’s point of view and connects it to the repository docs and public navigation APIs that participate in preloading and pending route matching. Sources: docs/router/config.json, docs/router/api/router/LinkOptionsType.md, docs/router/api/router/MatchRouteOptionsType.md
TanStack Router treats data loading as part of routing rather than as an afterthought in individual components. That design is visible in the documentation spine: the Router docs are organized around routing guides, navigation, search params, router events, and API reference pages rather than only component examples. The same docs configuration also places TanStack Start beside Router as the full-stack layer, which matters because Start builds on Router’s loading model when applications move from client-only routing to server rendering, streaming, and server functions. Sources: docs/router/config.json, docs/start/config.json
The main reader problem is knowing where loader behavior begins and where navigation behavior takes over. A loader decides what data is needed for a route match; navigation APIs decide when a target route is discovered, matched, preloaded, or committed. Link options expose preload, preloadDelay, activeOptions, and disabled, which are not loader options themselves, but they directly influence when route data can be prepared before the user completes navigation. Match route options expose a pending switch so matching logic can reason about the pending destination, not only the currently committed location. Sources: docs/router/api/router/LinkOptionsType.md, docs/router/api/router/MatchRouteOptionsType.md
Relevant Source Files
docs/router/config.json— Defines the Router documentation structure, including Getting Started, Installation Guides, Routing, Guides, and API reference areas that frame data loading as part of the broader Router learning path.docs/start/config.json— Defines the TanStack Start documentation structure, showing where Router-driven data loading connects to full-stack topics such as routing, execution model, server functions, server components, and rendering.docs/router/api/router/ActiveLinkOptionsType.md— DocumentsActiveLinkOptions, including active and inactive anchor props layered on top ofLinkOptions; this is useful when representing loading-aware navigation affordances in link UI.docs/router/api/router/linkOptions.md— Documents thelinkOptionshelper, which type-checks object literals intended forLink,navigate, orredirectand supports reusable, strongly typed navigation targets.docs/router/api/router/LinkOptionsType.md— DocumentsLinkOptions, includingpreload,preloadDelay,activeOptions,target, anddisabled, which are the supplied API evidence for route preloading behavior.docs/router/api/router/MatchRouteOptionsType.md— DocumentsMatchRouteOptions, includingpending,includeSearch, andfuzzy, which are relevant when matching a current or pending route during data-aware navigation.
Core Loader Model
A data-loading route usually has three conceptual pieces: the route match, the loader inputs, and the loaded output. The route match identifies which route in the tree is being prepared. Loader inputs commonly include route params, validated search state, route context, and loader dependencies. The loaded output becomes route data that the route component can read without independently rediscovering how to fetch it. This keeps route components focused on rendering while the router coordinates when data should be fetched, reused, refreshed, or invalidated as navigation changes.
Loader dependencies are the values that determine whether a loader result is still valid for a route match. In a typed Router app, those dependencies often come from the same URL state that drives routing: path params, search params, and context. When those values change, the loader should be considered against a different dependency set. That is why data loading belongs near the route definition: the route already knows the URL pattern, params, search validation, and parent context that make the loader meaningful. A component-only fetch would need to reconstruct those relationships manually.
Caching is the layer that makes this model efficient. Once a loader resolves for a given route and dependency set, the router can reuse that data for subsequent matching or navigation until the cache policy says it is stale or invalidated. Stale times give an application a way to trade freshness for responsiveness: a short stale time favors refetching often, while a longer stale time favors instant navigation and reuse. Invalidation is the explicit escape hatch for mutations and external changes, allowing the app to tell the router that previously loaded data should no longer be trusted.
Navigation, Preloading, and Pending State
Preloading is the bridge between user intent and loader execution. The supplied LinkOptions reference defines preload as accepting strategies including false, intent, viewport, and render, and defines preloadDelay as a delay for intent preloading that can be cancelled if intent exits before the delay completes. For data loading, the practical effect is that route code and route data can be prepared before the click becomes a committed navigation, reducing the time the destination spends in a pending state. Sources: docs/router/api/router/LinkOptionsType.md
linkOptions supports this workflow by letting teams define a navigation target once and reuse it in Link, navigate, or redirect calls. The documented example type-checks a target with a nested search object, then spreads the result into Link. That matters for loaders because typed search state is often part of loader dependency calculation. A reusable link-options object keeps route intent, search shape, and navigation target together instead of duplicating them across links, redirects, and imperative navigation helpers. Sources: docs/router/api/router/linkOptions.md
Pending state is how the UI understands that the router is moving toward a destination that may still be matching, loading, or otherwise preparing. The MatchRouteOptions reference includes a pending option that matches against the pending location instead of the current location. It also includes includeSearch for deep inclusive search matching and fuzzy for matching broader route prefixes. These options are useful for loading-aware navigation UI, such as highlighting the destination branch while its loader is still resolving or checking whether pending search state includes a specific filter. Sources: docs/router/api/router/MatchRouteOptionsType.md
API Components
| Component | Public contract from supplied docs | Data-loading relevance |
|---|---|---|
LinkOptions | Extends navigation options with anchor-aware fields such as target, activeOptions, preload, preloadDelay, and disabled. | Controls when route targets can be preloaded and how link behavior participates in data-aware navigation. |
linkOptions(props) | Type-checks an object literal intended for Link, navigate, or redirect and returns the inferred exact object type. | Lets applications share typed route targets, params, and search inputs that commonly feed loader dependencies. |
ActiveLinkOptions | Extends LinkOptions with activeProps and inactiveProps. | Helps render navigation state around current or pending routes, including routes whose data is being prepared. |
MatchRouteOptions | Supports pending, deprecated caseSensitive, includeSearch, and fuzzy. | Lets code compare against pending destinations and search state while loaders are in flight. |
The active-link API is not a data-fetching API, but it is part of the user experience around data loading. ActiveLinkOptions extends LinkOptions and adds activeProps and inactiveProps, each accepting anchor attributes or a function returning anchor attributes. This lets an app style a link according to active state while keeping the same typed navigation options that may include preloading. In practice, applications often combine active styling with pending matching so users can see both where they are and where the router is preparing to go. Sources: docs/router/api/router/ActiveLinkOptionsType.md, docs/router/api/router/LinkOptionsType.md
Execution Flow
A typical data-aware navigation starts before the user sees a final screen. First, a link or imperative navigation target identifies a destination route and typed URL inputs. If the link has an eager enough preload strategy, the router can begin preparing the destination when the link renders, enters the viewport, or receives user intent. Next, route matching determines the branch of the route tree and the loader dependency values for each matched route. Then loaders can resolve, reuse cached data, or be marked stale depending on policy and invalidation state.
During the transition, the UI should avoid guessing from local component state alone. Match options make it possible to ask whether the pending location matches a route, optionally including search params and fuzzy matching. That distinction is important for nested layouts: a parent layout may remain visible while a child route is pending, or a sidebar may want to highlight the pending branch before the current location changes. Pending matching gives these components a router-level view of navigation progress, which is more accurate than testing the current URL during an in-flight navigation.
After the navigation commits, route components read the data associated with their route matches. If the same route and dependency values are reached again before data becomes stale, cached loader data can be reused. If a user performs a mutation, changes a search filter, or otherwise changes the inputs that a loader depends on, the app should invalidate or navigate with new dependencies so the loader result reflects the new state. This cycle keeps URL state, route matching, and loaded data aligned.
Practical Patterns and Next Steps
For data-heavy apps, prefer a route-first mental model. Put data requirements near the route that owns the screen, use typed params and search values as loader inputs, and configure caching according to how quickly that data changes. Use link preloading for destinations where users are likely to navigate next, especially list-to-detail flows, dashboards, and tabbed screens. Use preloadDelay for intent-based links when you want to avoid work from accidental hovers while still making deliberate navigation feel instant. Sources: docs/router/api/router/LinkOptionsType.md, docs/router/api/router/linkOptions.md
When debugging loader behavior, separate four questions: did the route match, did the dependency set change, did the cache consider the data fresh, and is the UI looking at the current or pending location? The supplied APIs help with the last question directly through MatchRouteOptions.pending and with preloading through LinkOptions.preload. For deeper implementation details, continue to the related pages on search params, preloading and caching, external data loading and mutations, and router hooks. Those pages complete the story by showing how URL state, invalidation, and data access are wired into application code.