Skip to main content
rule

nextjs-tanstack-query-cursorrules-prompt-file — Cursor rule

A Cursor rule for Next.js App Router and TanStack Query v5, with cache hydration, Server Actions as mutations, and optimistic updates.

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

What nextjs-tanstack-query-cursorrules-prompt-file does

This rule guides Cursor through Next.js App Router projects that use TypeScript and TanStack Query v5. It assigns initial data loading to Server Components, while Client Components use TanStack Query for interactive requests, mutations, polling, and optimistic UI changes.

The guidance covers QueryClientProvider, HydrationBoundary, server-side prefetching, and shared query keys. It treats Server Actions as mutation functions and uses route handlers under src/app/api/ as query targets. It also describes rollback handling and infinite queries.

The rule targets **/*, but alwaysApply: false means Cursor does not apply it to every request. The project needs Next.js App Router and TanStack Query v5.

What it helps with

  • hydrate the query cache from server-rendered data
  • use Server Actions as TanStack Query mutations
  • roll back an optimistic post update after an error
  • separate Server Component and Client Component data work
  • build infinite scrolling with route handlers

Example prompts for the agent

  • Set up a posts page in Next.js App Router with HydrationBoundary and a prefetched TanStack Query cache.
  • Connect the post-creation Server Action to a mutation and invalidate the list after success.
  • Add an optimistic post edit with rollback when the server action fails.

How to install nextjs-tanstack-query-cursorrules-prompt-file

Manually: put the files in place

.cursor/rules/nextjs-tanstack-query-cursorrules-prompt-file.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-cursorrules-prompt-file

How do I install nextjs-tanstack-query-cursorrules-prompt-file in Cursor?

Place the rule file at .cursor/rules/nextjs-tanstack-query-cursorrules-prompt-file.mdc.

What does nextjs-tanstack-query-cursorrules-prompt-file do?

It tells Cursor how to combine Server Components, Client Components, and TanStack Query v5 in Next.js App Router. The rule covers hydration, Server Actions, mutations, and optimistic updates.

Why does the rule use HydrationBoundary?

A Server Component prefetches the data, then passes the Query cache state into a Client Component through HydrationBoundary. The client can read the prefetched state on its first render.

Source text

nextjs-tanstack-query-cursorrules-prompt-file.mdc · CC0-1.0
---
description: "Cursor rules for Next.js App Router with TanStack Query v5, covering the HydrationBoundary pattern, Server Actions as mutations, and optimistic updates."
globs: **/*
alwaysApply: false
---
You 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'`

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

Similar IDE rules

All IDE rules for Frontend and UI →

From the same set210

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 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. ★ 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 nextjs-react-typescript-cursorrules-prompt-file A Cursor rule that tells the agent how to write code in Next.js 14 App Router, React and TypeScript projects. ★ 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