PluginBench
Rule

nextjs tanstack query

via PatrickJS/awesome-cursorrules

Next.js App Router + TanStack Query v5 with HydrationBoundary, Server Actions, and optimistic updates

What is nextjs tanstack query?

Integrates TanStack Query v5 into Next.js App Router by prefetching data in Server Components, hydrating the client cache via HydrationBoundary, and using Server Actions as mutation handlers. Use this when building interactive pages that need server-side initial data, real-time updates, optimistic UI, or infinite scroll.

  • Prefetch queries in Server Components and hydrate client cache to eliminate waterfalls
  • Use Server Actions as mutationFn for mutations with automatic revalidation
  • Implement optimistic updates with onMutate/onError/onSettled lifecycle
  • Support infinite scroll and polling in Client Components with pre-populated cache
  • Manage separate QueryClient instances per request (server) and per session (browser)
  • Combine React Server Components for initial data with TanStack Query for interactive state

Applies to

File patterns this rule matches.

["app/**/*.tsx"
app/**/*.ts
src/app/**/*.tsx
"src/queries/**/*"]
Rule definition (reference)

Source of truth, from the repository.

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

// 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

// 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

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

Server Actions as mutationFn

// 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

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

Related rules

Next.js App Router + TanStack Query v5 integration with HydrationBoundary, Server Actions, and optimistic updates.

**/*
41k
via PatrickJS/awesome-cursorrules

Next.js and TypeScript setup for Autonomys network block explorer development.

**/*
41k
via PatrickJS/awesome-cursorrules

Next.js + TypeScript development rules with shadcn/ui, Tailwind, and Python LLM integration.

**/*
41k
via PatrickJS/awesome-cursorrules

Next.js + TypeScript + Tailwind CSS setup for Autonomys block explorer development.

**/*
41k
via PatrickJS/awesome-cursorrules

Next.js + Vercel + Supabase stack guide for BA Copilot, a BPMN diagram assistant for business analysts.

**/*
41k
via PatrickJS/awesome-cursorrules

Next.js + TypeScript + Vercel development with AI SDK RSC, middleware, and KV database integration.

**/*
41k
via PatrickJS/awesome-cursorrules