Cursor Rules
react tanstack router query - Claude MCP Skill
You are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, Vite, and building fully type-safe single-page applications.
SEO Guide: Enhance your AI agent with the react tanstack router query tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to you are an expert in react, tanstack router v1, tanstack query v5, typescript, vite, and building fu... Download and configure this skill to unlock new capabilities for your AI workflow.
Documentation
SKILL.mdYou are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, Vite, and building fully type-safe single-page applications.
# React + TanStack Router + TanStack Query Guidelines
## Architecture Overview
- TanStack Router handles all routing, URL state, and navigation
- TanStack Query manages all server state, caching, and async data
- React components are pure UI — they read from Query cache and trigger mutations
- Loaders bridge Router and Query: they prefetch into the Query cache before render
- This eliminates loading spinners for route-level data; Suspense handles component-level loading
## Project Setup
```
src/
routes/
__root.tsx
index.tsx
posts/
index.tsx
$postId.tsx
queries/ ← Query definitions (queryOptions factories)
posts.ts
users.ts
api/ ← API client functions (fetchers)
posts.ts
users.ts
lib/
queryClient.ts
router.ts
main.tsx
```
## QueryClient + Router Setup
```ts
// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
retry: (count, error: any) => error?.status !== 404 && count < 2,
},
},
})
```
```tsx
// src/lib/router.ts
import { createRouter } from '@tanstack/react-router'
import { routeTree } from '../routeTree.gen'
import { queryClient } from './queryClient'
export const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
```
```tsx
// src/main.tsx
import { RouterProvider } from '@tanstack/react-router'
import { QueryClientProvider } from '@tanstack/react-query'
import { router } from './lib/router'
import { queryClient } from './lib/queryClient'
ReactDOM.createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
)
```
## Query Definitions (queryOptions factories)
- Co-locate query key, fetcher, and staleTime in one place
- Share between Router loaders and component hooks
```ts
// src/queries/posts.ts
import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query'
import { fetchPost, fetchPosts } from '../api/posts'
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
export const postDetailQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetchPost(id),
staleTime: 1000 * 60 * 5,
})
export const postsListQueryOptions = (filters?: PostFilters) =>
queryOptions({
queryKey: postKeys.list(filters),
queryFn: () => fetchPosts(filters),
staleTime: 1000 * 60,
})
```
## Router Loader + Query Integration
- Loaders call `queryClient.ensureQueryData` — populates cache, renders immediately without spinner
- Components then call `useQuery` with the same options — reads from cache synchronously
```tsx
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { postDetailQueryOptions } from '../../queries/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postDetailQueryOptions(params.postId)),
errorComponent: ({ error }) => <ErrorMessage error={error} />,
pendingComponent: PostSkeleton,
component: PostDetail,
})
function PostDetail() {
const { postId } = Route.useParams()
// data is already in cache from loader — no loading state
const { data: post } = useQuery(postDetailQueryOptions(postId))
return <article><h1>{post!.title}</h1></article>
}
```
## Search Params + Query Integration
- Use TanStack Router search params as the source of truth for filter/pagination state
- Pass search params into queryOptions to drive query key and fetcher
```tsx
// src/routes/posts/index.tsx
import { createFileRoute, Link } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'
import { postsListQueryOptions } from '../../queries/posts'
const searchSchema = z.object({
page: z.number().int().min(1).default(1),
category: z.string().optional(),
})
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
loader: ({ context: { queryClient }, location: { search } }) =>
queryClient.ensureQueryData(postsListQueryOptions(search)),
component: PostsList,
})
function PostsList() {
const search = Route.useSearch()
const navigate = Route.useNavigate()
const { data: posts } = useQuery(postsListQueryOptions(search))
return (
<div>
{posts?.map(post => (
<Link key={post.id} to="/posts/$postId" params={{ postId: post.id }}>
{post.title}
</Link>
))}
<button onClick={() => navigate({ search: { ...search, page: search.page + 1 } })}>
Next Page
</button>
</div>
)
}
```
## Mutations
```tsx
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from '@tanstack/react-router'
import { postKeys } from '../../queries/posts'
function CreatePostForm() {
const queryClient = useQueryClient()
const navigate = useNavigate()
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (newPost) => {
// Populate detail cache immediately
queryClient.setQueryData(postKeys.detail(newPost.id), newPost)
// Invalidate list queries
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
// Navigate to new post (no loading — cache is warm)
navigate({ to: '/posts/$postId', params: { postId: newPost.id } })
},
})
return (/* form JSX */)
}
```
## Authentication Pattern
```tsx
// src/routes/__root.tsx
import { createRootRouteWithContext } from '@tanstack/react-router'
export interface RouterContext {
queryClient: QueryClient
auth: { isAuthenticated: boolean; user: User | null }
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
})
// src/routes/_auth.tsx (pathless layout for protected routes)
export const Route = createFileRoute('/_auth')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login', search: { redirect: location.pathname } })
}
},
})
```
## Prefetching on Hover
```tsx
function PostCard({ post }: { post: Post }) {
const queryClient = useQueryClient()
return (
<Link
to="/posts/$postId"
params={{ postId: post.id }}
onMouseEnter={() => queryClient.prefetchQuery(postDetailQueryOptions(post.id))}
>
{post.title}
</Link>
)
}
```
## DevTools (Development Only)
```tsx
// In __root.tsx
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// Inside component
{import.meta.env.DEV && (
<>
<TanStackRouterDevtools position="bottom-left" />
<ReactQueryDevtools buttonPosition="bottom-right" />
</>
)}
```
## Key Rules
- Always define `queryOptions` outside of components — not inline in `useQuery()`
- Never use `useEffect` to fetch data — use loaders or `useQuery`
- Always type router context — `declare module '@tanstack/react-router'` registration is required
- Search params are the only source of truth for URL-driven filter state
- Mutations should `setQueryData` + `invalidateQueries`, not just invalidate, for instant UI feedbackSignals
Information
- Repository
- PatrickJS/awesome-cursorrules
- Author
- PatrickJS
- Last Sync
- 5/10/2026
- Repo Updated
- 5/10/2026
- Created
- 5/5/2026
Reviews (0)
No reviews yet. Be the first to review this skill!
Related Skills
upgrade-nodejs
Upgrading Bun's Self-Reported Node.js Version
cursorrules
CrewAI Development Rules
cn-check
Install and run the Continue CLI (`cn`) to execute AI agent checks on local code changes. Use when asked to "run checks", "lint with AI", "review my changes with cn", or set up Continue CI locally.
CLAUDE
CLAUDE.md
Related Guides
Bear Notes Claude Skill: Your AI-Powered Note-Taking Assistant
Learn how to use the bear-notes Claude skill. Complete guide with installation instructions and examples.
Mastering tmux with Claude: A Complete Guide to the tmux Claude Skill
Learn how to use the tmux Claude skill. Complete guide with installation instructions and examples.
OpenAI Whisper API Claude Skill: Complete Guide to AI-Powered Audio Transcription
Learn how to use the openai-whisper-api Claude skill. Complete guide with installation instructions and examples.