machichdigital
RegelCursor RulesLizenz: CC0 1.0frei kopierbar

React Tanstack Router Query

Cursor-Regel für TanStack Router und TanStack Query zusammen — typsicheres Routing plus Datenfetching.

⬇ Als Datei laden

× kopiert× heruntergeladenBewertung:

Cursor-Regel für TanStack Router und TanStack Query zusammen — typsicheres Routing plus Datenfetching.

Original-Beschreibung der Autoren: Cursor rules for React SPAs combining TanStack Router v1 and TanStack Query v5 for zero-loading-spinner routing and type-safe server state.

Die Regel

---
description: "Cursor rules for React SPAs combining TanStack Router v1 and TanStack Query v5 for zero-loading-spinner routing and type-safe server state."
globs: **/*
alwaysApply: false
---
You 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,
    },
  },
})
// 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
  }
}
// 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
// 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
// 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
// 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

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
      queryClie
… (hier gekürzt — Kopieren/Download liefert die vollständige Regel)

So nutzt du sie

Die Regel kopieren (Button oben) oder als Datei herunterladen und im Projekt unter .cursor/rules/ ablegen — Cursor lädt sie beim nächsten Start automatisch. Ältere Cursor-Versionen lesen alternativ eine einzelne .cursorrules-Datei im Projektstamm; dort einfach den Regel-Text ohne den Kopfblock zwischen den ----Zeilen einfügen.

Der Regel-Text ist englisch — Cursor versteht ihn unabhängig von der Sprache, in der Sie mit dem Editor chatten.

Im Detail

Kombiniert Vorgaben für TanStack Router (typsicheres, dateibasiertes Routing) und TanStack Query (Server-State-Caching) in einem React-Projekt. Der Nutzen liegt darin, dass beide Bibliotheken eng zusammenspielen können — etwa Route-Loader, die TanStack-Query-Caches vorab befüllen —, und die Regel zeigt Cursor, wie dieses Zusammenspiel idiomatisch aussieht. Relevant für neuere Projekte, die bewusst auf das TanStack-Ökosystem statt React Router/klassisches React Query setzen. Für Projekte mit nur einer der beiden Bibliotheken ist die kombinierte Regel eventuell zu viel, und eine spezifischere Einzel-Regel passt besser.

Praxis-Tipp

Bei Prompts zu neuen Routen explizit erwähnen, ob der Loader die Query per queryClient.ensureQueryData vorladen soll, damit Cursor das Zusammenspiel korrekt umsetzt.

Lizenz & Quelle

Inhalt ansehen (react-tanstack-router-query.mdc)
Lade …

Erfahrungen & Kommentare.

Funktioniert der Regel bei Ihnen? Tipps, Stolperfallen, Varianten — teilen Sie es mit der Community.

Lade Kommentare …

Ihre IP-Adresse wird zum Schutz vor Missbrauch gespeichert und nach 14 Tagen automatisch entfernt (Datenschutz).

Passt dazu.