External Data Loading and Mutations

Purpose and Scope

External data loading is the pattern of using a route loader to read data from a system outside the route module itself: an HTTP API, a database client, a TanStack Query QueryClient, a generated SDK, or any domain service you pass through router context. In TanStack Router, the route tree is treated as the application contract, so external reads should still be anchored to route definitions, URL params, search state, and loader dependencies rather than being scattered across components. That keeps navigation, preloading, pending UI, and route data access aligned with the URL.

Sources: docs/router/api/router.md

This page explains how to think about loaders and mutations together. A loader answers, “What data must be available for this route match?” A mutation answers, “What external state changed, and which route data should be refreshed afterward?” The Router API index exposes the pieces that participate in that flow: route creation functions such as createFileRoute and createRoute, data-facing hooks such as useLoaderData and useLoaderDeps, router access through useRouter, and boundary utilities such as redirect, notFound, defer, and <Await>. Those names are the stable public vocabulary to use when designing integrations.

Sources: docs/router/api/router.md

Relevant Source Files

  • docs/router/api/router.md — Lists the public Router API groups used by this topic, including route creation functions, loader/data hooks, router hooks, deferred-data components, redirect/not-found helpers, and Router option/state/type references.

Core Data-Loading Primitives

The most important boundary is the route. createFileRoute, createRoute, createRootRoute, and createRootRouteWithContext are the public entry points that let route modules declare behavior against the route tree. When the data source is external, keep the loader close to the route that owns the URL segment and derive its input from route params, validated search, route context, or loader dependencies. This makes the loader deterministic from the router’s point of view and lets the same route contract power links, navigation, matching, and data reads.

Sources: docs/router/api/router.md

Once a loader has produced data, components should read it through router APIs rather than duplicating the fetch. The API index names useLoaderData for consuming resolved loader output and useLoaderDeps for reading the dependency values that caused a loader to run. useMatch, useMatches, useParams, and useSearch complete the picture when a component needs to understand the current match, URL params, or search state while rendering data-heavy UI. In practice, this means the external system is wrapped at the route edge, while the component tree consumes typed router state.

Sources: docs/router/api/router.md

Deferred and exceptional data paths are also first-class. The API index includes defer, <Await>, and useAwaited, which are the public terms for splitting slower data from the initial route render. It also includes redirect, notFound, isRedirect, isNotFound, <CatchBoundary>, <CatchNotFound>, <ErrorComponent>, and <NotFoundComponent>. Use these helpers to turn external-service responses into router-aware control flow: authentication failures can redirect, missing records can become not-found states, and unexpected service failures can be handled by route error boundaries.

Sources: docs/router/api/router.md

Loader Integration Flow

A good loader integration starts by choosing the source of truth for each input. Path params identify route resources such as an invoice id. Search params represent URL-owned state such as filters and pagination. Router or route context is the right place for shared clients, such as an API client or query cache, because it avoids importing environment-specific singletons into every route. The official Router positioning describes loaders, cache, prefetch, pending UI, params, search schemas, and generated route maps as one connected contract; treat external data clients as implementations behind that contract rather than as a replacement for it.

Sources: docs/router/api/router.md

A typical file-route loader wraps the external call, normalizes errors into router behavior, and returns only the data shape the route needs. The exact client is application-specific, but the router-facing shape stays consistent: route creation declares the loader, the loader uses URL-derived inputs, and the route component reads the result through useLoaderData. If the external data source supports caching, the loader can delegate to that cache while still letting the router decide when route data is needed for a match or preload.

import { createFileRoute, notFound, redirect } from '@tanstack/react-router'
 
export const Route = createFileRoute('/projects/$projectId')({
  loader: async ({ params, context }) => {
    const project = await context.api.projects.get(params.projectId)
 
    if (!context.auth.user) {
      throw redirect({ to: '/login' })
    }
 
    if (!project) {
      throw notFound()
    }
 
    return { project }
  },
  component: ProjectRoute,
})
 
function ProjectRoute() {
  const { project } = Route.useLoaderData()
  return <h1>{project.name}</h1>
}

Sources: docs/router/api/router.md

Mutations and Router Invalidation

Mutations should be designed as write operations followed by a route-data refresh decision. The Router API index exposes useRouter, useRouterState, navigation APIs, route matching APIs, and data hooks; together they let a component perform a write, inspect where the user is, and coordinate the next render through the router. In many apps the mutation itself belongs to a form handler, TanStack Query mutation, RPC client, or server function, while the router remains responsible for making matched route data current after the write completes.

Sources: docs/router/api/router.md

The key design choice is scope. If a mutation changes the exact resource loaded by the current route, refresh the current route data after the write. If it changes a list route, navigate back to that list or refresh the list route’s dependencies. If it changes auth or tenant context, consider invalidating broader route data because loaders may depend on router context. When the write creates a new resource, mutation completion often pairs with useNavigate, <Navigate>, or redirect-style navigation so the URL moves to the newly created resource route.

Sources: docs/router/api/router.md

function RenameProjectButton() {
  const router = Route.useRouter()
  const { project } = Route.useLoaderData()
 
  return (
    <button
      onClick={async () => {
        await api.projects.rename(project.id, 'New name')
        await router.invalidate()
      }}
    >
      Rename project
    </button>
  )
}

The example shows the common shape: perform the external mutation first, then ask the router to refresh route data before relying on the next render. Keep error handling around the external write itself, and reserve router error helpers for route-level control flow. If the mutation result already contains the full next state, you may update an external cache immediately and then use router invalidation as the consistency step that reconciles route loaders with the external source.

Sources: docs/router/api/router.md

API Components Reference

Public APIRole in external data and mutation flows
createFileRoute, createRouteDefine the route boundary where loaders connect URL inputs to external reads.
createRouterCreates the router instance that owns the route tree, options, state, and runtime behavior.
useLoaderDataReads data returned by the matched route loader.
useLoaderDepsReads dependency values used to decide loader execution.
useRouterGives components access to the router instance for refresh, navigation coordination, and stateful workflows.
useRouterStateObserves router state when mutation UI needs pending or match-aware decisions.
useParams, useSearchReads URL-owned inputs that commonly parameterize external requests.
defer, <Await>, useAwaitedSupports slower external data without blocking every part of the route UI.
redirect, notFound, isRedirect, isNotFoundConverts external-service outcomes into router-recognized control flow.
<CatchBoundary>, <CatchNotFound>, <ErrorComponent>Renders route-aware error and not-found states for failed external reads.

Sources: docs/router/api/router.md

Implementation Guidance

Prefer a narrow adapter between the router and each external system. A route loader should not expose transport concerns such as raw response objects unless the route genuinely needs them. Instead, parse, authorize, and normalize at the loader boundary, then return a stable object for the component. This approach also makes pending and error states easier to reason about, because the router sees a single async boundary and the UI reads a single loader result. When multiple routes need the same external client, inject it through router context rather than constructing it inside every loader.

Sources: docs/router/api/router.md

For mutation-heavy screens, write down the relationship between each command and the route data it affects. A create command might navigate to a detail route. An update command might invalidate the current detail route and any parent list. A delete command might redirect or navigate away before refreshing the remaining route tree. The Router API index gives you the vocabulary for each part of that workflow: navigation APIs for URL changes, loader hooks for data reads, router hooks for instance access, and error helpers for route-level failures.

Sources: docs/router/api/router.md

Next Steps

If you are building a data-heavy route, start with the route contract before choosing the fetching library. Define the route path, params, search state, loader dependencies, and component data needs, then connect the loader to your external source. After that, add mutation handlers and decide exactly when route data should be invalidated or navigation should occur. For adjacent topics, read the Data Loading page for loader caching and pending behavior, Search Params for URL-owned loader inputs, Router Context for dependency injection, and the Errors, Redirects, and Search Utilities reference for route-aware control flow.

Sources: docs/router/api/router.md