react tanstack router query
via PatrickJS/awesome-cursorrules
Zero-loading-spinner React SPA with TanStack Router v1 + TanStack Query v5 for type-safe URLs and cache-first data.
What is react tanstack router query?
Establishes the definitive pattern for combining TanStack Router (routing & URL state) with TanStack Query (server state & caching) in React SPAs. Loaders prefetch data into the Query cache before render, eliminating loading spinners. Use this rule when building data-driven single-page applications that prioritize instant navigation and type-safe routing.
- Prefetch route data via loaders into Query cache before component render for zero loading spinners
- Define reusable queryOptions outside components to ensure type safety and cache consistency
- Map search params to Query keys for filter and pagination state management
- Warm the cache after mutations and invalidate stale queries for instant UI feedback
- Enable hover prefetching on links to preload data on user intent
Applies to
File patterns this rule matches.
Rule definition (reference)
Source of truth, from the repository.
You are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, and Vite.
Architecture
- TanStack Router: routing, URL state, navigation
- TanStack Query: server state, caching, mutations
- Loader = bridge: prefetches into Query cache before render → zero loading spinners for route data
- Components are pure UI: read from Query cache, trigger mutations
Setup
// src/lib/queryClient.ts
export const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 60_000 } },
})
// src/lib/router.ts
export const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
})
declare module '@tanstack/react-router' {
interface Register { router: typeof router }
}
// src/main.tsx
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
Query Definitions
// src/queries/posts.ts
export const postKeys = {
all: ['posts'] as const,
detail: (id: string) => [...postKeys.all, 'detail', id] as const,
list: (f?: PostFilters) => [...postKeys.all, 'list', f] as const,
}
export const postQueryOptions = (id: string) =>
queryOptions({ queryKey: postKeys.detail(id), queryFn: () => fetchPost(id) })
export const postsQueryOptions = (filters?: PostFilters) =>
queryOptions({ queryKey: postKeys.list(filters), queryFn: () => fetchPosts(filters) })
Loader + Component (zero loading state)
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId)),
component: PostDetail,
})
function PostDetail() {
const { postId } = Route.useParams()
const { data: post } = useQuery(postQueryOptions(postId)) // always in cache from loader
return <h1>{post!.title}</h1>
}
Search Params → Query Key
const searchSchema = z.object({ page: z.number().default(1), q: z.string().optional() })
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
loader: ({ context: { queryClient }, location: { search } }) =>
queryClient.ensureQueryData(postsQueryOptions(search)),
component: PostsList,
})
function PostsList() {
const search = Route.useSearch()
const { data } = useQuery(postsQueryOptions(search))
// ...
}
Mutations
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (newPost) => {
queryClient.setQueryData(postKeys.detail(newPost.id), newPost) // warm cache
queryClient.invalidateQueries({ queryKey: postKeys.list() })
navigate({ to: '/posts/$postId', params: { postId: newPost.id } }) // instant — no spinner
},
})
Hover Prefetching
<Link
to="/posts/$postId"
params={{ postId: post.id }}
onMouseEnter={() => queryClient.prefetchQuery(postQueryOptions(post.id))}
>
{post.title}
</Link>
Key Rules
- Always define
queryOptionsoutside components — never inline insideuseQuery() - Never use
useEffectfor data fetching — use loaders oruseQuery - Search params are the single source of truth for filter/pagination state
- After mutations:
setQueryData+invalidateQueriesfor instant UI feedback declare module '@tanstack/react-router'router registration is required for full type safety
Related rules
Zero-loading-spinner routing with TanStack Router v1 and Query v5 for type-safe React SPAs
Expert guidance for React, TypeScript, Next.js 14, and Web3 stack development with Wagmi and Viem.
Full-stack React, TypeScript, and Symfony development assistant with Docker expertise.
React and TypeScript state management guidance for Zustand stores, selectors, middleware, persistence, and testing.
Write READMEs that convert readers in 3-5 seconds with landing-page structure and working code examples.

ROS and ROS2 best practices for packages, nodes, interfaces, timing, and testing.