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.
On July 15th, 2023, while scrolling through Twitter, I came across a tweet from the React Query team announcing a new package. This package makes working with React Query in the Next.js App Router much easier. Excited about this update, I have now included the recommended approach in the article.
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
- Recommended Approach: Using Client Stream Hydration
- Previous WorkArounds
- Create Links to the Pages
- Conclusion
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.
1`yarn create next-app nextjs13-react-query
2# or
3npx create-next-app@latest nextjs13-react-query`
Shell
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.
Recommended Approach: Using Client Stream Hydration
When I first wrote this article, there was no recommended way to use React Query in the new Next.js 13 app directory. However, the community came up with some workarounds, which involve handling the hydration yourself or passing the initial data to a client component from a server component. Fortunately, the React Query team has recently published an experimental package that makes working with React Query in the Next.js 13 App Router a breeze.
With this new experimental package, you do not need to manually handle hydration or pass the initialData
as a prop from a server component to a client component. When the initial request is made, the data will be fetched on the server, which means the API request from the useQuery
hook is first initialized on the server to fetch the data. Once the data is ready, it will be automatically made available in the QueryClient on the client.
To install the package, run the following command:
1`# For yarn
2yarn add @tanstack/react-query-next-experimental
3
4# For PNPM
5pnpm add @tanstack/react-query-next-experimental
6
7# For NPM
8npm i @tanstack/react-query-next-experimental`
Shell
Create a Client Query Client Provider
To use the installed package, we need to wrap the ReactQueryStreamedHydration
and QueryClientProvider
components around the entry point of our Next.js application. However, since all components within the app directory are server components by default, attempting to do so will result in errors because the QueryClientProvider
can only be initialized on the client.
To resolve this, we must create a client-side provider where we can initialize the components. Then, we can safely wrap the client-side provider around the root children
without encountering any errors.
To begin, let’s create a utils
directory within the src
directory. Within the utils
folder, create a provider.tsx
file and insert the following code:
src/utils/provider.tsx
1`"use client";
2
3import React from "react";
4import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
5import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
6import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
7
8function Providers({ children }: React.PropsWithChildren) {
9 const [client] = React.useState(new QueryClient());
10
11 return (
12 <QueryClientProvider client={client}>
13
14 <ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
15
16 <ReactQueryDevtools initialIsOpen={false} />
17
18 </QueryClientProvider>
19 );
20}
21
22export default Providers;`
React TSX
The ReactQueryStreamedHydration
component from the experimental package enables the streaming of data from the server to the client.
Wrap the Provider Around the Root Children Prop
Next, we need to wrap the client-side provider we created earlier around the children
prop of the root layout component. This ensures that the QueryClient
is made available to all the client-side components within the app. To do this, open the src/app/layout.tsx
file and make the following modifications:
src/app/layout.tsx
1`import Providers from "@/utils/provider";
2import React from "react";
3// import "./globals.css";
4
5export const metadata = {
6 title: "Create Next App",
7 description: "Generated by create next app",
8};
9
10export default function RootLayout({
11 children,
12}: {
13 children: React.ReactNode;
14}) {
15 return (
16 <html lang="en">
17
18 <body>
19
20 <Providers>{children}</Providers>
21
22 </body>
23
24 </html>
25 );
26}`
React TSX
Use React Query for Data Fetching
Now we can start using useQuery
from React Query to fetch data in our Next.js app. The only requirement is to set suspense: true
in the useQuery
options for this recommended approach to work. Here’s an example of how to use useQuery
to fetch a list of users and display them in the UI:
src/app/hydration-stream-suspense/list-users.tsx
1``"use client";
2
3import { User } from "../types";
4import { useQuery } from "@tanstack/react-query";
5
6async function getUsers() {
7 return (await fetch("https://jsonplaceholder.typicode.com/users").then(
8 (res) => res.json()
9 )) as User[];
10}
11
12export default function ListUsers() {
13 const { data, isLoading, isFetching, error } = useQuery<User[]>({
14 queryKey: ["hydrate-users"],
15 queryFn: () => getUsers(),
16 suspense: true,
17 staleTime: 5 * 1000,
18 });
19
20 return (
21 <>
22
23 {error ? (
24 <p>Oh no, there was an error</p>
25 ) : isFetching || isLoading ? (
26 <p style={{ textAlign: "center" }}>loading... on the client-side</p>
27 ) : data ? (
28 <div
29 style={{
30 display: "grid",
31 gridTemplateColumns: "1fr 1fr 1fr 1fr",
32 gap: 20,
33 }}
34 >
35
36 {data?.map((user) => (
37 <div
38 key={user.id}
39 style={{ border: "1px solid #ccc", textAlign: "center" }}
40 >
41
42 <img
43 src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
44 alt={user.name}
45 style={{ width: 180, height: 180 }}
46 />
47
48 <h3>{user.name}</h3>
49
50 </div>
51 ))}
52
53 </div>
54 ) : null}
55
56 </>
57 );
58}``
React TSX
Wrap the Client Component in React Suspense
To render the ListUsers
component we created earlier, we need to include it in the JSX of a page file. Since we included suspense: true
in the useQuery
options, we must wrap the <Suspense>
component from React around the ListUsers
component in order for the streaming of data from the server to the client to work.
src/app/hydration-stream-suspense/page.tsx
1`import Counter from "./counter";
2import ListUsers from "./list-users";
3import { Suspense } from "react";
4
5export default async function Page() {
6 return (
7 <main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
8
9 <Counter />
10
11 <Suspense
12 fallback={
13 <p style={{ textAlign: "center" }}>loading... on initial request</p>
14 }
15 >
16
17 <ListUsers />
18
19 </Suspense>
20
21 </main>
22 );
23}`
React TSX
If you want to see React Query in action in the new Next.js app directory, you can clone the project from https://github.com/wpcodevo/nextjs13-react-query and open it in your text editor. Take a look at the code or start the Next.js development server and open the application in your browser to test the different ways of using React Query in Next.js 13.

To verify if the data is initially fetched on the server, manually refresh the browser and check the terminal where the dev server is running. You should see the API request logged in the terminal, confirming that the data is being fetched on the server.

Previous WorkArounds
The content within this section describes the previous ways we used React Query in the app directory. These approaches involved using tricks to make hydration work or passing initial data from a server component to a client component.
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
1`"use client";
2
3import React from "react";
4import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
5import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
6
7function Providers({ children }: React.PropsWithChildren) {
8 const [client] = React.useState(
9 new QueryClient({ defaultOptions: { queries: { staleTime: 5000 } } })
10 );
11
12 return (
13 <QueryClientProvider client={client}>
14
15 {children}
16
17 <ReactQueryDevtools initialIsOpen={false} />
18
19 </QueryClientProvider>
20 );
21}
22
23export default Providers;`
React TSX
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
1`import { QueryClient } from "@tanstack/query-core";
2import { cache } from "react";
3
4const getQueryClient = cache(() => new QueryClient());
5export default getQueryClient;`
React TSX
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
1`"use client";
2
3import { Hydrate as RQHydrate, HydrateProps } from "@tanstack/react-query";
4
5function Hydrate(props: HydrateProps) {
6 return <RQHydrate {...props} />;
7}
8
9export default Hydrate;`
React TSX
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
1`import Providers from "@/utils/provider";
2import React from "react";
3// import "./globals.css";
4
5export const metadata = {
6 title: "Create Next App",
7 description: "Generated by create next app",
8};
9
10export default function RootLayout({
11 children,
12}: {
13 children: React.ReactNode;
14}) {
15 return (
16 <html lang="en">
17
18 <body>
19
20 <Providers>{children}</Providers>
21
22 </body>
23
24 </html>
25 );
26}`
React TSX
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
1`export type User = {
2 id: number;
3 name: string;
4 email: string;
5};`
TypeScript
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
1``"use client";
2
3import { User } from "../types";
4import { useQuery } from "@tanstack/react-query";
5import React from "react";
6
7async function getUsers() {
8 const res = await fetch("https://jsonplaceholder.typicode.com/users");
9 const users = (await res.json()) as User[];
10 return users;
11}
12
13export default function ListUsers() {
14 const [count, setCount] = React.useState(0);
15
16 const { data, isLoading, isFetching, error } = useQuery({
17 queryKey: ["hydrate-users"],
18 queryFn: () => getUsers(),
19 });
20
21 return (
22 <main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
23
24 <div style={{ marginBottom: "4rem", textAlign: "center" }}>
25
26 <h4 style={{ marginBottom: 16 }}>{count}</h4>
27
28 <button onClick={() => setCount((prev) => prev + 1)}>increment</button>
29
30 <button
31 onClick={() => setCount((prev) => prev - 1)}
32 style={{ marginInline: 16 }}
33 > decrement </button>
34
35 <button onClick={() => setCount(0)}>reset</button>
36
37 </div>
38
39 {error ? (
40 <p>Oh no, there was an error</p>
41 ) : isLoading || isFetching ? (
42 <p>Loading...</p>
43 ) : data ? (
44 <div
45 style={{
46 display: "grid",
47 gridTemplateColumns: "1fr 1fr 1fr 1fr",
48 gap: 20,
49 }}
50 >
51
52 {data.map((user) => (
53 <div
54 key={user.id}
55 style={{ border: "1px solid #ccc", textAlign: "center" }}
56 >
57
58 <img
59 src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
60 alt={user.name}
61 style={{ height: 180, width: 180 }}
62 />
63
64 <h3>{user.name}</h3>
65
66 </div>
67 ))}
68
69 </div>
70 ) : null}
71
72 </main>
73 );
74}``
React TSX
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
1`import getQueryClient from "@/utils/getQueryClient";
2import Hydrate from "@/utils/hydrate.client";
3import { dehydrate } from "@tanstack/query-core";
4import ListUsers from "./list-users";
5import { User } from "../types";
6
7async function getUsers() {
8 const res = await fetch("https://jsonplaceholder.typicode.com/users");
9 const users = (await res.json()) as User[];
10 return users;
11}
12
13export default async function Hydation() {
14 const queryClient = getQueryClient();
15 await queryClient.prefetchQuery(["hydrate-users"], getUsers);
16 const dehydratedState = dehydrate(queryClient);
17
18 return (
19 <Hydrate state={dehydratedState}>
20
21 <ListUsers />
22
23 </Hydrate>
24 );
25}`
React TSX
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
1``"use client";
2
3import { User } from "../types";
4import { useQuery } from "@tanstack/react-query";
5import React from "react";
6
7async function getUsers() {
8 const res = await fetch("https://jsonplaceholder.typicode.com/users");
9 const users = (await res.json()) as User[];
10 return users;
11}
12
13export default function ListUsers({ users }: { users: User[] }) {
14 const [count, setCount] = React.useState(0);
15
16 const { data, isLoading, isFetching, error } = useQuery({
17 queryKey: ["initial-users"],
18 queryFn: () => getUsers(),
19 initialData: users,
20 });
21 return (
22 <main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
23
24 <div style={{ marginBottom: "4rem", textAlign: "center" }}>
25
26 <h4 style={{ marginBottom: 16 }}>{count}</h4>
27
28 <button onClick={() => setCount((prev) => prev + 1)}>increment</button>
29
30 <button
31 onClick={() => setCount((prev) => prev - 1)}
32 style={{ marginInline: 16 }}
33 > decrement </button>
34
35 <button onClick={() => setCount(0)}>reset</button>
36
37 </div>
38
39 {error ? (
40 <p>Oh no, there was an error</p>
41 ) : isLoading || isFetching ? (
42 <p>Loading...</p>
43 ) : data ? (
44 <div
45 style={{
46 display: "grid",
47 gridTemplateColumns: "1fr 1fr 1fr 1fr",
48 gap: 20,
49 }}
50 >
51
52 {data.map((user) => (
53 <div
54 key={user.id}
55 style={{ border: "1px solid #ccc", textAlign: "center" }}
56 >
57
58 <img
59 src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
60 alt={user.name}
61 style={{ height: 180, width: 180 }}
62 />
63
64 <h3>{user.name}</h3>
65
66 </div>
67 ))}
68
69 </div>
70 ) : null}
71
72 </main>
73 );
74}``
React TSX
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
1`import ListUsers from "./list-users";
2import { User } from "../types";
3
4async function getUsers() {
5 const res = await fetch("https://jsonplaceholder.typicode.com/users");
6 const users = (await res.json()) as User[];
7 return users;
8}
9
10export default async function InitialData() {
11 const users = await getUsers();
12
13 return <ListUsers users={users} />;
14}`
React TSX
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
1`import Link from "next/link";
2
3export default function Home() {
4 return (
5 <>
6
7 <h1>Hello, Next.js 13 App Directory!</h1>
8
9 <p>
10
11 <Link href="/initial-data">Prefetching Using initial data</Link>
12
13 </p>
14
15 <p>
16
17 <Link href="/hydration">Prefetching Using Hydration</Link>
18
19 </p>
20
21 </>
22 );
23}`
React TSX
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!