Written by

amitkys

At

Tue Aug 04 2026

Tanstack Query Implementation in Nextjs

Back

Implementation

Query Provider

Data can we exposed via query client from one user to another. To prevent that ... Create a file named query-provider.tsx and add the following code.

components/query-provider.tsx
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";

// Create a new QueryClient
function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // Don't refetch immediately after hydration
        staleTime: 60 * 1000,
      },
    },
  });
}

// Reuse one QueryClient in the browser
let browserQueryClient: QueryClient | undefined;

function getQueryClient() {
  // Server: create a new QueryClient for every request
  if (typeof window === "undefined") {
    return makeQueryClient();
  }

  // Browser: reuse the same QueryClient
  if (!browserQueryClient) {
    browserQueryClient = makeQueryClient();
  }

  return browserQueryClient;
}

export default function QueryProvider({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <QueryClientProvider client={getQueryClient()}>
      {children}
    </QueryClientProvider>
  );
}

Update Layout.tsx file

app/layout.tsx
import "./globals.css";
import { Providers } from "@/components/query-provider"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Files Structure

we are gonna have mainly 2 files, one for GET data, another for.. if we mutate data on Server like, update, create, delete (this all come in data modification), so this operation goes to mut-post.tsx

get-post.tsx
mut-post.tsx

Create the following files & routes:

import { queryOptions, useQuery } from "@tanstack/react-query";
import axios from "axios";

interface Post {
userId: number;
id: number;
title: string;
body: string;
}

export function getPostsQuery() {
return queryOptions({
queryKey: ["get-posts"],
queryFn: async () => {
  try {

    const { data } = await axios.get<Post>("https://jsonplaceholder.typicode.com/posts/1");
    return data;
  }
  catch (e) {
    if (axios.isAxiosError(e)) {
      console.error("axios error", e.response?.status);
      console.error("response", e.response?.data)
      console.error("message", e.message)
    }
    throw e;
  }
}
});
}

export function useGetPostsQuery() {
return useQuery(getPostsQuery())
}

data fetching on client side

page.tsx
"use client";

import { useGetPostsQuery } from "./query/get-post";

export default function Page() {
  const { isPending, isError, data, error } = useGetPostsQuery();
  if (isPending) {
    return (
      <div>Loading...</div>
    )
  }

  if (isError) {
    return (
      <div> Error: {error?.message}</div>
    )
  }
  if (!data) {
    return <p>data not found</p>
  }
  return (
    <div> {data.title} </div>
  )
}

data fetching on server side

app/home/page.tsx
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query"
import { getPostsQuery } from "./query/get-post"
import { ClientComponent } from "./_components/client-component"
export default async function Page() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery(getPostsQuery());
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <ClientComponent />
    </HydrationBoundary>
  )
}