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.
.cursor/rules/nextjs-tanstack-query.mdcWhat 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.tsxthat prefetches posts withprefetchQueryandHydrationBoundary - Implement a mutation for
createPostusing a Server Action and invalidate the posts cache - Add an optimistic update for
updatePostwith rollback logic inonError
How to install nextjs-tanstack-query
Manually: put the files in place
.cursor/rules/nextjs-tanstack-query.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/nextjs-tanstack-query.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/nextjs-tanstack-query.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/nextjs-tanstack-query.mdManually: put the files in place
.clinerules/nextjs-tanstack-query.mdStep 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