Cursor Rules
nextjs tanstack query - Claude MCP Skill
You are an expert in Next.js (App Router), TanStack Query v5, TypeScript, and combining server components with client-side data fetching.
SEO Guide: Enhance your AI agent with the nextjs tanstack query tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to you are an expert in next.js (app router), tanstack query v5, typescript, and combining server compo... Download and configure this skill to unlock new capabilities for your AI workflow.
Documentation
SKILL.mdYou are an expert in Next.js (App Router), TanStack Query v5, TypeScript, and combining server components with client-side data fetching.
# Next.js App Router + TanStack Query v5 Guidelines
## Architecture Philosophy
- Server Components fetch data directly (no TanStack Query needed there)
- TanStack Query lives in Client Components for interactive, real-time, or user-triggered data
- Use React Server Components for initial page data; TanStack Query for mutations, polling, and optimistic updates
- Hydrate the Query cache from server to avoid client waterfalls on first load
## Provider Setup with Hydration
```tsx
// src/providers/query-provider.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { useState } from 'react'
export function QueryProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
retry: (count, error: any) => error?.status !== 404 && count < 2,
},
},
}),
)
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
// src/app/layout.tsx
import { QueryProvider } from '@/providers/query-provider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<QueryProvider>{children}</QueryProvider>
</body>
</html>
)
}
```
## Hydration Pattern (Server → Client Cache)
- Prefetch in Server Components, dehydrate state, rehydrate in client
- This eliminates client-side loading states on first render
```tsx
// src/app/posts/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { postsQueryOptions } from '@/queries/posts'
import { PostsList } from './_components/posts-list'
export default async function PostsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(postsQueryOptions())
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsList />
</HydrationBoundary>
)
}
// src/app/posts/_components/posts-list.tsx
'use client'
import { useQuery } from '@tanstack/react-query'
import { postsQueryOptions } from '@/queries/posts'
export function PostsList() {
// Reads from pre-populated cache — no loading spinner
const { data: posts } = useQuery(postsQueryOptions())
return <ul>{posts?.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
```
## Query Definitions
```ts
// src/queries/posts.ts
import { queryOptions } from '@tanstack/react-query'
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 postsQueryOptions = (filters?: PostFilters) =>
queryOptions({
queryKey: postKeys.list(filters),
queryFn: () => fetch(`/api/posts`).then(r => r.json()),
})
export const postDetailQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetch(`/api/posts/${id}`).then(r => r.json()),
staleTime: 1000 * 60 * 5,
})
```
## Server Actions + Mutations
- Use Next.js Server Actions as the `mutationFn` in TanStack Query mutations
- This gives you type-safe server mutations WITH optimistic update/rollback capabilities
```tsx
// src/app/posts/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(data: { title: string; body: string }) {
const post = await db.post.create({ data })
revalidatePath('/posts')
return post
}
// src/app/posts/_components/create-post-form.tsx
'use client'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { createPost } from '../actions'
import { postKeys } from '@/queries/posts'
export function CreatePostForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
},
})
return (
<button
onClick={() => mutation.mutate({ title: 'New Post', body: '...' })}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Creating...' : 'Create Post'}
</button>
)
}
```
## Optimistic Updates with Server Actions
```tsx
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (updated) => {
await queryClient.cancelQueries({ queryKey: postKeys.detail(updated.id) })
const previous = queryClient.getQueryData(postKeys.detail(updated.id))
queryClient.setQueryData(postKeys.detail(updated.id), (old: Post) => ({ ...old, ...updated }))
return { previous }
},
onError: (_, updated, ctx) => {
queryClient.setQueryData(postKeys.detail(updated.id), ctx?.previous)
},
onSettled: (_, __, updated) => {
queryClient.invalidateQueries({ queryKey: postKeys.detail(updated.id) })
},
})
```
## When to Use Server Components vs TanStack Query
| Use Server Components When | Use TanStack Query When |
|---|---|
| Static or rarely-changing data | Real-time or frequently-updated data |
| SEO-critical initial content | User interactions (forms, toggles) |
| No need to refetch on client | Optimistic updates needed |
| Data is not shared across components | Data is shared across many components |
| No loading states desired | Fine-grained loading/error UI needed |
## Route Handlers (API Routes) as Query Targets
- Use `src/app/api/` route handlers as the API layer for TanStack Query fetchers
- Keep route handlers thin — just parse/validate input and call service layer
```ts
// src/app/api/posts/route.ts
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const posts = await getPosts({ category: searchParams.get('category') })
return NextResponse.json(posts)
}
```
## Infinite Queries (Pagination / Infinite Scroll)
```tsx
'use client'
import { useInfiniteQuery } from '@tanstack/react-query'
export function InfinitePosts() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: postKeys.lists(),
queryFn: ({ pageParam }) =>
fetch(`/api/posts?cursor=${pageParam ?? ''}`).then(r => r.json()),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
const posts = data?.pages.flatMap(p => p.items) ?? []
return (
<div>
{posts.map(post => <PostCard key={post.id} post={post} />)}
<button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
</div>
)
}
```
## Key Rules
- Create one `QueryClient` per request on the server side (inside Server Components)
- Create one `QueryClient` per browser session on the client (via `useState` in provider)
- Always use `HydrationBoundary` when passing server-prefetched data to client components
- Never call `fetch` inside Client Components directly — always go through `queryFn`
- Mark all components that use TanStack Query hooks with `'use client'`Signals
Information
- Repository
- PatrickJS/awesome-cursorrules
- Author
- PatrickJS
- Last Sync
- 5/12/2026
- Repo Updated
- 5/12/2026
- Created
- 5/5/2026
Reviews (0)
No reviews yet. Be the first to review this skill!
Related Skills
cursorrules
CrewAI Development Rules
firecrawl-build-onboarding
Get Firecrawl credentials and SDK setup into a project. Use when an application needs `FIRECRAWL_API_KEY`, when an agent should add Firecrawl to `.env`, when the user wants to authenticate Firecrawl for app code, or when choosing the first SDK and docs for a new Firecrawl integration. This skill includes its own browser auth flow, so it does not depend on the website onboarding skill.
firecrawl-build
Integrate Firecrawl into application code whenever a product, agent, or workflow needs web data inside the app — web search, live search results, page scraping, structured extraction, or browser interaction. Use when building any feature that needs data from the web in code, even if the user does not mention Firecrawl explicitly and only describes wanting web data, website content, search, scraping, or interaction in an application. Trigger for Firecrawl requests, "fire girl" shorthand, and generic app-level web-data needs that should map to `/scrape`, `/search`, or `/interact`. Do not use this skill for one-off terminal-only web tasks during the current session; use `firecrawl/cli` for those.
firecrawl-build-scrape
Integrate Firecrawl `/scrape` into product code for single-page extraction. Use when an app already has a URL and needs markdown, HTML, links, screenshots, metadata, or structured page output. Prefer this skill over broader crawl patterns when the feature is page-level.
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.