Skip to main content
rule

nextjs-tanstack-query — Cursor rule: what it does, how to install

Enforces architecture patterns for Next.js App Router with TanStack Query v5. Guides Cursor on cache hydration, Server Actions as mutations, and optimistic UI updates.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/nextjs-tanstack-query.mdc
Open on GitHub

What nextjs-tanstack-query does

This rule standardizes Next.js App Router workflows with TanStack Query v5. Server Components fetch data directly without query hooks. Prefetched queries pass to the client inside HydrationBoundary. Each server request must instantiate a new QueryClient. Reusing an instance across requests leaks data between users.

Direct fetch calls in client components are forbidden. All requests must go through queryOptions factories. Mutations invoke Server Actions inside mutationFn, triggering invalidateQueries on success. Optimistic updates include rollback logic in onError.

The rule targets app/**/*.tsx, app/**/*.ts, src/app/**/*.tsx, and src/queries/**/*. Any file using TanStack Query hooks requires the 'use client' directive.

What it helps with

  • hydrate server-prefetched data into client components
  • run Next.js Server Actions as TanStack Query mutations
  • implement optimistic UI updates with error rollbacks
  • generate query configurations using queryOptions factories

Example prompts for the agent

  • Create a server component in app/posts/page.tsx that prefetches posts with prefetchQuery and HydrationBoundary
  • Implement a mutation for createPost using a Server Action and invalidate the posts cache
  • Add an optimistic update for updatePost with rollback logic in onError

How to install nextjs-tanstack-query

Manually: put the files in place

.cursor/rules/nextjs-tanstack-query.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about nextjs-tanstack-query

How to install nextjs-tanstack-query in Cursor?

Place the rule file at .cursor/rules/nextjs-tanstack-query.mdc inside your project root. Cursor will apply its instructions to matching application and query files.

What does the nextjs-tanstack-query rule do?

It guides Cursor on integrating TanStack Query v5 with Next.js App Router. It prevents cross-request data leaks and enforces clean hydration patterns.

Why must QueryClient be recreated on every server request?

Sharing an instance across server requests causes cache leakage between different users. The rule requires a brand new QueryClient for every incoming server request.

Source text

nextjs-tanstack-query.mdc · CC0-1.0
---
description: "Next.js App Router combined with TanStack Query v5 — HydrationBoundary pattern, Server Actions as mutations, optimistic updates, and infinite scroll"
globs: ["app/**/*.tsx", "app/**/*.ts", "src/app/**/*.tsx", "src/queries/**/*"]
alwaysApply: false
---
You are an expert in Next.js App Router, TanStack Query v5, TypeScript, and combining server components with client-side data fetching.

## Architecture
- Server Components fetch data directly — no TanStack Query needed there
- TanStack Query lives in Client Components for interactive, real-time, or mutation-driven data
- Hydrate the Query cache from server to avoid client waterfalls on first load
- Use React Server Components for initial page data; TanStack Query for mutations + polling + optimistic UI

## Provider Setup
```tsx
// providers/query-provider.tsx
'use client'
export function QueryProvider({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: { queries: { staleTime: 60 * 1000 } },
  }))
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}
```

## Server Prefetch + HydrationBoundary Pattern
```tsx
// app/posts/page.tsx  (Server Component)
export default async function PostsPage() {
  const queryClient = new QueryClient()
  await queryClient.prefetchQuery(postsQueryOptions())
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <PostsList />
    </HydrationBoundary>
  )
}

// app/posts/_components/posts-list.tsx  (Client Component)
'use client'
export function PostsList() {
  const { data: posts } = useQuery(postsQueryOptions())  // reads from pre-populated cache
  return <ul>{posts?.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
```

## queryOptions Factory
```ts
export const postsQueryOptions = (filters?: PostFilters) =>
  queryOptions({
    queryKey: ['posts', 'list', filters],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
  })
```

## Server Actions as mutationFn
```tsx
// app/posts/actions.ts
'use server'
export async function createPost(data: { title: string; body: string }) {
  const post = await db.post.create({ data })
  revalidatePath('/posts')
  return post
}

// usage in Client Component
const mutation = useMutation({
  mutationFn: createPost,
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts', 'list'] }),
})
```

## Optimistic Updates
```tsx
const mutation = useMutation({
  mutationFn: updatePost,
  onMutate: async (updated) => {
    await queryClient.cancelQueries({ queryKey: ['posts', 'detail', updated.id] })
    const previous = queryClient.getQueryData(['posts', 'detail', updated.id])
    queryClient.setQueryData(['posts', 'detail', updated.id], (old: Post) => ({ ...old, ...updated }))
    return { previous }
  },
  onError: (_, updated, ctx) => {
    queryClient.setQueryData(['posts', 'detail', updated.id], ctx?.previous)
  },
  onSettled: (_, __, updated) => {
    queryClient.invalidateQueries({ queryKey: ['posts', 'detail', updated.id] })
  },
})
```

## Key Rules
- Create a **new** `QueryClient` per request in Server Components — never reuse across requests
- Create **one** `QueryClient` per browser session via `useState` in the provider
- Always wrap server-prefetched subtrees in `HydrationBoundary`
- Mark all components using TanStack Query hooks with `'use client'`
- Never call `fetch` directly in Client Components — always go through `queryFn`

© PatrickJS · CC0-1.0 · PatrickJS/awesome-cursorrules

Similar IDE rules

All IDE rules for Frontend and UI →

From the same set210

nextjs-vercel-typescript-cursorrules-prompt-file A Cursor rule that sets code style and file structure for Next.js projects with TypeScript, Vercel middleware and a KV database. ★ 41,024repo stars nextjs-vercel-supabase-cursorrules-prompt-file Configures Cursor for building the BA Copilot app with Next.js App Router, Vercel AI, Supabase, and bpmn-js. Keeps the AI aligned with the chosen stack. ★ 41,024repo stars nextjs-typescript-tailwind-cursorrules-prompt-file A Cursor rule that hands the agent the conventions of a Next.js, TypeScript and Tailwind CSS project. Useful when new code has to match what is already there. ★ 41,024repo stars nextjs-typescript-cursorrules-prompt-file A Cursor rule that pins the Next.js and TypeScript stack and the agent's working order: plan first, one file at a time, fixed reply format. ★ 41,024repo stars nextjs-tanstack-query-cursorrules-prompt-file A Cursor rule for Next.js App Router and TanStack Query v5, with cache hydration, Server Actions as mutations, and optimistic updates. ★ 41,024repo stars nextjs-tailwind-typescript-apps-cursorrules-prompt A Cursor rule that points the assistant at Next.js, Tailwind CSS and TypeScript. It sets the working order: plan in pseudocode first, code after. ★ 41,024repo stars nextjs-supabase-shadcn-pwa-cursorrules-prompt-file Defines Next.js 15+, Supabase, and Shadcn UI conventions for Cursor. It caps files at 150 lines and enforces task logging in project docs. ★ 41,024repo stars nextjs-seo-dev-cursorrules-prompt-file A Cursor rule for Next.js work with SEO context, comment-preservation guidance, and a fixed project setup. ★ 41,024repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor