Framework-style loader/clientLoader route data only pays for itself with SSR/streaming — which a pure SPA doesn't have [6]. Pick RTK Query if you want OpenAPI codegen, else TanStack Query for the lighter footprint [9]. Choose the router for its routing — and if it's TanStack Router ⭐ 15k, use loaders only to prime the query cache with the router's own cache turned off [3].
loader / clientLoaderfetch route datauseLoaderData → Componentaction + <Form>auto-revalidates every loader on the pageloader: ensureQueryDatafire-and-forget · don't awaituseSuspenseQuery → Componentreads the primed querydefaultPreloadStaleTime: 0useQuery / RTK hookon mount| Concern | A · Router loaders | B · TanStack Router + Query | C · Query lib + thin router |
|---|---|---|---|
| Fetch trigger | Route match, before render | Loader primes, component reads | Component (hook) mount |
| Cache owner | The router | TanStack Query | Query lib |
| Cross-route sharing | ✗ per-route only | ✓ shared query cache | ✓ shared query cache |
| Invalidation | Auto-revalidate all loaders on mutation | invalidateQueries / tags |
Tags (RTK) / keys (TSQ) |
| OpenAPI codegen | ✗ hand-wired | community (orval / openapi) | ✓ RTK Query official |
| Pays off mainly with | SSR / streaming | either, best with SSR | CSR SPA |
| Router footprint | ⭐ 56k | ⭐ 15k | ⭐ 7.9k (wouter) |
defaultPreloadStaleTime: 0 so the router never caches [3]await, don't read useLoaderData — read via useSuspenseQuery so the query is GC/refetch-eligible [3]React Router mirrors the browser — “interrupted navigations … cancel in-flight data requests” and it “commits all fresh revalidation responses and cancels stale requests” [7]. A type-ahead via fetcher.submit never shows stale results.
Caveats: v7 re-runs loaders by default after mutations — add shouldRevalidate to stop unwanted refetches [12]; fetcher revalidation still has documented race edge-cases [8]; backend integrity is out of scope [7].
TanStack Query handles the same class structurally: keys identify data, in-flight requests dedupe, stale responses are ignored, and mutation → invalidation is explicit and cross-route [9].
Loader-based streaming (deferred data + Await) is a server-rendering feature — its value is flushing HTML while data resolves. TanStack's Query integration exists largely to “automate SSR dehydration/hydration and streaming” [4].
In a CSR SPA there is no HTML stream, so this benefit evaporates. You're left with client Suspense boundaries — which useSuspenseQuery gives you without any router loader at all [3].
Waterfalls in model B are avoided by not suspending in the loader and using useDeferredValue to keep old UI while new data loads [5].
RTK Query if OpenAPI codegen matters (official generator → typed endpoints + tag invalidation) [10], else TanStack Query for the lighter footprint and strongest cache [9]. Both command large 2026 adoption — Query ~12M weekly npm downloads [11].
TanStack Router ⭐ 15k gives type-safe params/search you feed straight into query keys. If you use its loaders, use them only to ensureQueryData with defaultPreloadStaleTime: 0, and read via useSuspenseQuery — never useLoaderData for the payload [3]. A minimal router (wouter ⭐ 7.9k) is a fine model-C pick if you don't need loader-level prefetch.
Which React router to adopt in 2026: a decision framework for a client-rendered admin SPA — across five angles, one recurring fact: keep data-fetching out of the router either way.
TanStack Router is the sharpest fit, React Router the safest default; Wouter for simple apps, Next.js App Router the wrong grain.
TanStack Router is the only router with typed, schema-validated search-param state end to end.
A standalone router on Vite is still a first-class 2026 choice; RSC solves problems this app doesn't have.
Already on React Router? Staying is near-drop-in. Greenfield SPA? The math tilts toward TanStack Router.