PluginBench
Rule

react tanstack router query

via PatrickJS/awesome-cursorrules

Type-safe React SPA routing with TanStack Router v1 + Query v5 for zero-loading-spinner data prefetching.

What is react tanstack router query?

Combines TanStack Router's loader pattern with TanStack Query v5 to prefetch route data into cache before render, eliminating loading spinners and enabling instant navigation. Use this when building data-driven SPAs that require type-safe URLs, cache-first data patterns, and seamless user experience.

  • Prefetch route data via loaders into Query cache before component render for zero loading states
  • Define reusable queryOptions with hierarchical query keys for consistent cache management
  • Synchronize search params with query keys for filter and pagination state
  • Warm cache after mutations and invalidate stale queries for instant UI feedback
  • Enable hover-based prefetching on links to anticipate user navigation
  • Enforce type-safe route params and search validation with Zod schemas

Applies to

File patterns this rule matches.

["src/routes/**/*"
src/queries/**/*
src/lib/router.ts
"src/lib/queryClient.ts"]
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 queryOptions outside components — never inline inside useQuery()
  • Never use useEffect for data fetching — use loaders or useQuery
  • Search params are the single source of truth for filter/pagination state
  • After mutations: setQueryData + invalidateQueries for instant UI feedback
  • declare module '@tanstack/react-router' router registration is required for full type safety

Related rules

Senior full-stack TypeScript, React, Node.js guidance with clean architecture, testing, and WHY-oriented reasoning.

**/*
41k
via PatrickJS/awesome-cursorrules

Quantitative factor research skills for designing, evaluating, and mining alpha factors in equities markets.

**/*
41k
via PatrickJS/awesome-cursorrules

Android development with Jetpack Compose, clean architecture, and Material Design 3.

**/*
41k
via PatrickJS/awesome-cursorrules

Angular development with Novo Elements UI library using standalone components.

**/*
41k
via PatrickJS/awesome-cursorrules

Expert Angular 18 + TypeScript development with Jest, emphasizing clean code and performance.

**/*
41k
via PatrickJS/awesome-cursorrules

Manage Kubernetes clusters, add-ons, stacks, and credentials via the Ankra CLI platform.

**/*.sh +5
41k
via PatrickJS/awesome-cursorrules