Are you interested in using React Query in your Next.js 13 app directory? If so, you’ve come to the right place! In this article, I’ll guide you through the process of setting up React Query and making the QueryClient accessible to all components in the component tree.
Firstly, I’ll show you how to wrap the QueryClientProvider around the Children Node of the root layout component in the Next.js 13 app directory. This is important as it will ensure that all components in your app have access to the same query client.
You’ll then learn how to fetch initial data in a Server Component higher up in the component tree and pass it as a prop to your Client Component. Additionally, I’ll explain how to prefetch the query on the server, dehydrate the cache, and rehydrate it on the client.
Since Next.js 13 is still in beta and libraries are still being adjusted to work with it, React Query has faced some challenges when integrating with the Next.js 13 app directory. However, I have carefully reviewed all the solutions proposed by the React Query team on GitHub, and in this article, I’ll show you the right way to set up React Query in the new app directory of Next.js 13.
More practice:
- React Query and Axios: User Registration and Email Verification
- Forgot/Reset Passwords with React Query and Axios
- How to Setup tRPC API Server & Client with Next.js and Prisma
- tRPC Server API with Next.js & PostgreSQL: Access & Refresh Tokens
- Full-Stack Next.js tRPC App: User Registration & Login Example
- Build a tRPC CRUD API Example with Next.js

Setup the Next.js 13 Project
After following this tutorial, your folder and file structure will look like the one shown in the screenshot below.

In this tutorial, we will create a simple project that includes a counter app and a component for displaying a list of users. This project will showcase how React Query can be used in both server-side and client-side rendered components.

Now that we have an understanding of what we’ll be building, let’s get started by generating a Next.js 13 project. Navigate to any directory on your computer and open a terminal in that directory. Depending on your preferred package manager, you can run the following command to initiate the project scaffolding process.
yarn create next-app nextjs13-react-query
# or
npx create-next-app@latest nextjs13-react-query
During the process, you’ll be prompted to choose which features to enable for the project. Make sure to select “Yes” for TypeScript and ESLint. Additionally, you’ll be asked if you’d like to use the src/
directory and the experimental app/
directory. Select “Yes” for both options, and for the import alias, simply press Tab and Enter to accept.
Once you’ve answered all the questions, the Next.js 13 project will be generated, and all necessary dependencies will be installed. After everything is installed, you can open the project in your preferred IDE or text editor, such as VS Code.
Create a Custom Query Client Provider
By default, all components in Next.js 13 are rendered on the server but React Query doesn’t work with Server Components. Therefore, we need to create a custom provider that will render the QueryClientProvider within a Client Component.
To ensure that the custom provider component only renders on the client-side, we can add "use client";
at the top of the file. This tells the server to skip rendering the custom provider component and render it only on the client-side.
To create this component, navigate to the ‘src‘ directory and create a new folder named ‘utils‘. Inside the ‘utils‘ folder, create a file named provider.tsx
and add the following code.
src/utils/provider.tsx
"use client";
import React from "react";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
function Providers({ children }: React.PropsWithChildren) {
const [client] = React.useState(
new QueryClient({ defaultOptions: { queries: { staleTime: 5000 } } })
);
return (
<QueryClientProvider client={client}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default Providers;
Create a Request-scoped Instance of QueryClient
To prevent data from being shared across users and requests, while still ensuring that the QueryClient is only created once per request, we can create a request-scoped singleton instance of the QueryClient.
This will make prefetched queries available to all components further down the component tree, and allow us to fetch data within multiple Server Components and use <Hydrate>
in multiple places.
You can create a getQueryClient.ts
file in the ‘src/utils‘ directory and add the following code snippets in order to achieve this.
src/utils/getQueryClient.ts
import { QueryClient } from "@tanstack/query-core";
import { cache } from "react";
const getQueryClient = cache(() => new QueryClient());
export default getQueryClient;
Create a Custom Hydrate Component
In Next.js 13, there was an issue with prefetching data on the server using the <Hydrate>
method, which was raised in the React Query GitHub issues. When we use the <Hydrate>
component in a server component, we encounter an error. To work around this, we need to create a custom component that renders the <Hydrate>
component on the client-side using the "use client"
flag.
To create this custom hydrate component, simply create a file named hydrate.client.tsx
in the ‘src/utils‘ directory and add the following code.
src/utils/hydrate.client.tsx
"use client";
import { Hydrate as RQHydrate, HydrateProps } from "@tanstack/react-query";
function Hydrate(props: HydrateProps) {
return <RQHydrate {...props} />;
}
export default Hydrate;
Provide the QueryClient Provider to Next.js
Next, we’ll wrap the custom provider around the children
of the RootLayout
component to render the QueryClientProvider at the root. By doing so, all other Client Components across the app will have access to the query client.
As the RootLayout
is a Server Component, the custom provider can directly render the QueryClientProvider since it’s marked as a Client Component.
To implement this, simply replace the contents of the layout.tsx
file located in the ‘app‘ directory with the following code.
src/app/layout.tsx
import Providers from "@/utils/provider";
import React from "react";
// import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Please keep in mind that it is best practice to render providers as deep as possible in the component tree. You may have noticed that in our example, the Providers
component only wraps around the {children}
prop instead of the entire <html>
document. This allows Next.js to better optimize the static parts of your Server Components.
Prefetching Data Using Hydration and Dehydration
With React Query set up in our Next.js 13 project, we’re ready to demonstrate how it can be used to fetch data on the server, hydrate the state, dehydrate the cache, and rehydrate it on the client. To illustrate this, we’ll fetch a list of users from the https://jsonplaceholder.typicode.com/ API.
Since we’re using TypeScript, we’ll need to define the structure of the API’s response. To do this, create a types.ts
file in the ‘app‘ directory and add the following TypeScript code.
src/app/types.ts
export type User = {
id: number;
name: string;
email: string;
};
Create the Client-Side Component
Now let’s create a Client-side component that displays the list of users fetched from the https://jsonplaceholder.typicode.com/users endpoint.
When this component mounts, it can retrieve the dehydrated query cache if available but will refetch the query on the client if it has become stale since the time it was rendered on the server.
To create this component, navigate to the ‘app‘ directory and create a ‘hydration‘ folder. Inside the ‘hydration‘ folder, create a list-users.tsx
file with the following code.
src/app/hydration/list-users.tsx
"use client";
import { User } from "../types";
import { useQuery } from "@tanstack/react-query";
import React from "react";
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = (await res.json()) as User[];
return users;
}
export default function ListUsers() {
const [count, setCount] = React.useState(0);
const { data, isLoading, isFetching, error } = useQuery({
queryKey: ["hydrate-users"],
queryFn: () => getUsers(),
});
return (
<main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
<div style={{ marginBottom: "4rem", textAlign: "center" }}>
<h4 style={{ marginBottom: 16 }}>{count}</h4>
<button onClick={() => setCount((prev) => prev + 1)}>increment</button>
<button
onClick={() => setCount((prev) => prev - 1)}
style={{ marginInline: 16 }}
>
decrement
</button>
<button onClick={() => setCount(0)}>reset</button>
</div>
{error ? (
<p>Oh no, there was an error</p>
) : isLoading || isFetching ? (
<p>Loading...</p>
) : data ? (
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: 20,
}}
>
{data.map((user) => (
<div
key={user.id}
style={{ border: "1px solid #ccc", textAlign: "center" }}
>
<img
src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
alt={user.name}
style={{ height: 180, width: 180 }}
/>
<h3>{user.name}</h3>
</div>
))}
</div>
) : null}
</main>
);
}
Create the Page Component
Next, we’ll create a Server Component that prefetches the query and passes the prefetched data to the list-users.tsx
component.
When the Server Component renders, calls to useQuery
nested inside the <Hydrate>
Client Component will have access to the prefetched data that is provided in the state property.
To create this Server Component, go to the ‘hydration‘ directory and create a page.tsx
file, then add the following code:
src/app/hydration/page.tsx
import getQueryClient from "@/utils/getQueryClient";
import Hydrate from "@/utils/hydrate.client";
import { dehydrate } from "@tanstack/query-core";
import ListUsers from "./list-users";
import { User } from "../types";
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = (await res.json()) as User[];
return users;
}
export default async function Hydation() {
const queryClient = getQueryClient();
await queryClient.prefetchQuery(["hydrate-users"], getUsers);
const dehydratedState = dehydrate(queryClient);
return (
<Hydrate state={dehydratedState}>
<ListUsers />
</Hydrate>
);
}
Prefetching Data Using Initial Data
Let’s explore how to transfer prefetched data from a Server Component, located at a higher level in the component hierarchy, to a Client-side component using props.
However, note that this approach is not recommended by the React Query team, and you should always aim to use the hydration and dehydration method for better performance and reliability.
Create the Client-Side Component
To implement this approach, we’ll create a new Client-side component that will accept the prefetched data as a prop and render it in the UI. Once the data becomes stale, the component will use React Query to refetch the data as usual.
Create a new folder called initial-data
inside the ‘app‘ directory. Within this folder, create a file called list-users.tsx, and add the following code:
src/app/initial-data/list-users.tsx
"use client";
import { User } from "../types";
import { useQuery } from "@tanstack/react-query";
import React from "react";
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = (await res.json()) as User[];
return users;
}
export default function ListUsers({ users }: { users: User[] }) {
const [count, setCount] = React.useState(0);
const { data, isLoading, isFetching, error } = useQuery({
queryKey: ["initial-users"],
queryFn: () => getUsers(),
initialData: users,
});
return (
<main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
<div style={{ marginBottom: "4rem", textAlign: "center" }}>
<h4 style={{ marginBottom: 16 }}>{count}</h4>
<button onClick={() => setCount((prev) => prev + 1)}>increment</button>
<button
onClick={() => setCount((prev) => prev - 1)}
style={{ marginInline: 16 }}
>
decrement
</button>
<button onClick={() => setCount(0)}>reset</button>
</div>
{error ? (
<p>Oh no, there was an error</p>
) : isLoading || isFetching ? (
<p>Loading...</p>
) : data ? (
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: 20,
}}
>
{data.map((user) => (
<div
key={user.id}
style={{ border: "1px solid #ccc", textAlign: "center" }}
>
<img
src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
alt={user.name}
style={{ height: 180, width: 180 }}
/>
<h3>{user.name}</h3>
</div>
))}
</div>
) : null}
</main>
);
}
Create the Page Component
Let’s create a Server Component that prefetches the data and passes it as a prop to the list-users.tsx
component.
To do this, create a page.tsx
file in the initial-data
folder and include the following code:
src/app/initial-data/page.tsx
import ListUsers from "./list-users";
import { User } from "../types";
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = (await res.json()) as User[];
return users;
}
export default async function InitialData() {
const users = await getUsers();
return <ListUsers users={users} />;
}
Create Links to the Pages
Finally, it’s time to add links to the pages we have created so far on the Home page component. To do this, you can replace the contents of the src/app/page.tsx
file with the following code.
src/app/page.tsx
import Link from "next/link";
export default function Home() {
return (
<>
<h1>Hello, Next.js 13 App Directory!</h1>
<p>
<Link href="/initial-data">Prefetching Using initial data</Link>
</p>
<p>
<Link href="/hydration">Prefetching Using Hydration</Link>
</p>
</>
);
}
Congratulations! You have successfully set up React Query in a Next.js 13 app and implemented two different data prefetching methods.
To view the app, simply start the Next.js development server and navigate to http://localhost:3000/. The Home page will display two links: Prefetching Using initial data
and Prefetching Using Hydration
. Clicking on either link will take you to the respective page where you can see the data prefetching method in action.

Each page contains a counter component that enables you to increase, decrease, and reset the counter. Additionally, the list of users is fetched via React Query and displayed in a grid on the page.

Conclusion
You can access the React Query and Next.js 13 project’s source code on GitHub.
Throughout this article, you learned how to set up React Query in the Next.js 13 app directory and explored two different ways to prefetch data supported by React Query.
I hope this article was helpful and enjoyable for you. If you have any feedback or questions, please leave a comment below. Thank you for reading!
Good Job
Thank you! I’m glad you enjoyed the article.
Thanks for the explanation. I guess axios can no longer be used for queries now?
You can definitely still use Axios for your queries. However, since Fetch API is now recommended for use in React Server Components, it might make more sense to use it along with React Query on the client-side for your queries.
If you find that Fetch API is limited for your specific use case, then you could choose to use Axios instead. Currently, everything looks good for React Query, but the question of how to handle hydration in the new Next.js 13 app directory remains.
I appreciate your post.
I’m a beginner, so I don’t think I understand properly. can I ask you a question?
So in Next.js v13, it’s best to use fetchAPI, and if I feel the limit while using fetchAPI, can I consider Axios then?
The
fetch()
function provided in the Next.js app directory is a modified version of the Fetch API provided by the browser.It has some cool features like automatic request deduplication and the ability to set caching and revalidating options for each request.
Both the React and Next.js teams recommend using the
fetch()
function for data fetching, so you can take advantage of these features. If you want to fetch data in client components, you can use the newuse()
hook along withfetch()
. If you need more info, I suggest checking out https://nextjs.org/docs/app/building-your-application/data-fetching.thank you soooooooo much Edem
Is there a point of using React Query anymore since Nextjs 13 with App router gives you a loading page, error page, and also the fetch() on superpowers?
Even though the Next.js 13 app router provides pages for handling loading and error states, React Query still retains its advantages when it comes to intelligent refetching features and caching.
Moreover, React Query can be utilized to manage other forms of asynchronous data, such as WebSocket streams or local storage.
For me, I believe that the new features in Next.js 13’s app router are useful for handling basic loading and error states. As for the Fetch API, I am already enjoying it along with the new `use` hook.
hey, thanks for the work on this… I tried to migrate my custom boilerplate to the hydrate solution and it kinda works. But it seems when loading the page that the data is being fetched on the client side only initially. With page routing the data that is fetched was rendered the same time the rest was rendered. Now its popping up after the asynchronous fetch is done. Am I missing something?
I’m happy to help! However, it’s tough to determine what might be missing without seeing your code.
While there isn’t yet an officially recommended approach to hydration with React Query in Next.js 13, some developers have used
InitialData
with success, although it can lead to prop drilling in certain cases.I did come across this repository (https://github.com/2manoj1/poc-nextjs-13-react-query) that demonstrates how to implement Hydration with React Query in Next.js 13, which you might find helpful.
First , thanks for the post
I have a question ! What is the different between tanstack/query-core and tanstack/react-query ?
Is tanstack/react-query include code run on client ?
I’m glad you found the post helpful.
To answer your question,
@tanstack/query-core
and@tanstack/react-query
are both packages provided by the TanStack team, but they serve different purposes.@tanstack/query-core
is the core package of the TanStack Query library. It contains the essential functionality and utilities for managing asynchronous data in your application, including query fetching, caching, and mutation handling. It serves as the foundation for thereact-query
package.On the other hand,
@tanstack/react-query
is a React-specific package that builds on top of@tanstack/query-core
. It provides React hooks and components that allow you to easily integrate TanStack Query’s functionality into your React application.Great job Edem!
Thanks Morgan, I’m glad you found the article helpful.
Hi,
You may be able to inform me, I followed your tutorial but my data remains in ‘slate’ in the devtools
Do you know how i can fix this please ?
Thank u 🙂
That is the default behavior of React Query. When the data is fetched, it is immediately marked as
stale
. However, you have the power to modify this behavior according to your requirements.To address the issue, you can adjust the
staleTime
duration in React Query. For example, you can set it to 5000 milliseconds (or 5 seconds).By doing this, after the data is fetched, React Query will consider it fresh and usable for the next 5 seconds. During this time, it won’t trigger unnecessary refetches. After the 5 seconds pass, React Query will automatically initiate a background refresh to update the data.
To implement this change, you have a couple of options. You can specify the
staleTime
directly in the query by using theuseQuery
hook. Alternatively, you can set it as a default value for all queries by modifying the configuration object of theQueryClient
.With next 13.4.3 this line
await queryClient.prefetchQuery([‘key’], fetchApi) not working, failing with message saying createContext works only in client components.
queryClient is imported from the util (as defined in this tutorial)
Did you include the
"use client";
pragma at the beginning of the file where thequeryClient
is defined?What is the advantage of React Query while using static generation? We have here next cache and also infinity cacheTime on a server which is memory consumed as new client per request. IS there any reasons to rehydrate state of SSG page?
When you use React Query with static generation, you gain the benefits that React Query provides, such as efficient caching, improved performance, data consistency, and seamless client-side updates for your application.
In the case of rehydrating the state of the SSG page, it ensures that the data fetched on the server is available on the client-side during the initial request. This eliminates the need for additional data requests and allows the page to utilize the hydrated data.