Skip to content

Commit 5c48c7c

Browse files
authored
docs(ssr): add experimental app directory guide
1 parent a1d65df commit 5c48c7c

File tree

1 file changed

+163
-4
lines changed

1 file changed

+163
-4
lines changed

docs/guides/ssr.md

Lines changed: 163 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,19 @@ The exact implementation of these mechanisms may vary from platform to platform,
1818
- Static Generation (SSG)
1919
- Server-side Rendering (SSR)
2020

21+
> Only Server-side Rendering (SSR) is presently supported by React Server Components in Next.js 13's experimental `app` directory.
22+
2123
React Query supports both of these forms of pre-rendering regardless of what platform you may be using
2224

2325
### Using `initialData`
2426

27+
The setup is minimal and this can be a quick solution for some cases, but there are a **few tradeoffs to consider** when compared to the full approach:
28+
29+
- If you are calling `useQuery` in a component deeper down in the tree you need to pass the `initialData` down to that point
30+
- If you are calling `useQuery` with the same query in multiple locations, you need to pass `initialData` to all of them
31+
- There is no way to know at what time the query was fetched on the server, so `dataUpdatedAt` and determining if the query needs refetching is based on when the page loaded instead
32+
33+
#### With standard `pages` directory
2534
Together with Next.js's [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering), you can pass the data you fetch in either method to `useQuery`'s' `initialData` option. From React Query's perspective, these integrate in the same way, `getStaticProps` is shown below:
2635

2736
```tsx
@@ -41,16 +50,77 @@ function Posts(props) {
4150
}
4251
```
4352

44-
The setup is minimal and this can be a quick solution for some cases, but there are a **few tradeoffs to consider** when compared to the full approach:
53+
#### With experimental `app` directory
54+
Fetch your initial data in a Server Component higher up in the component tree, and pass it to your Client Component as a prop.
4555

46-
- If you are calling `useQuery` in a component deeper down in the tree you need to pass the `initialData` down to that point
47-
- If you are calling `useQuery` with the same query in multiple locations, you need to pass `initialData` to all of them
48-
- There is no way to know at what time the query was fetched on the server, so `dataUpdatedAt` and determining if the query needs refetching is based on when the page loaded instead
56+
```tsx
57+
// app/page.jsx
58+
export default async function RootPage() {
59+
const initialData = await getPosts()
60+
61+
return <Posts posts={initialData} />
62+
}
63+
```
64+
65+
```tsx
66+
// app/Posts.jsx
67+
'use client'
68+
69+
import { useQuery } from '@tanstack/react-query'
70+
71+
export function Posts(props) {
72+
const {data} = useQuery({
73+
queryKey: ['posts'],
74+
queryFn: getPosts,
75+
initialData: props.posts,
76+
})
77+
78+
// ...
79+
}
80+
```
81+
82+
The hooks provided by the `react-query` package need to retrieve a `QueryClient` from their context. Wrap your component tree with `<QueryClientProvider>` and pass it an instance of `QueryClient`.
83+
84+
```tsx
85+
// app/Providers.jsx
86+
'use client'
87+
88+
import { useState } from 'react'
89+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
90+
91+
export function Providers({children}) {
92+
const [queryClient] = useState(new QueryClient())
93+
94+
return (
95+
<QueryClientProvider client={queryClient}>
96+
{children}
97+
</QueryClientProvider>
98+
);
99+
}
100+
```
101+
102+
```tsx
103+
// app/layout.jsx
104+
import { Providers } from './Providers'
105+
106+
export default function RootLayout({children}) {
107+
return (
108+
<html>
109+
<head></head>
110+
<body>
111+
<Providers>{children}</Providers>
112+
</body>
113+
</html>
114+
);
115+
}
116+
```
49117

50118
### Using Hydration
51119

52120
React Query supports prefetching multiple queries on the server in Next.js and then _dehydrating_ those queries to the queryClient. This means the server can prerender markup that is immediately available on page load and as soon as JS is available, React Query can upgrade or _hydrate_ those queries with the full functionality of the library. This includes refetching those queries on the client if they have become stale since the time they were rendered on the server.
53121

122+
#### With standard `pages` directory
123+
54124
To support caching queries on the server and set up hydration:
55125

56126
- Create a new `QueryClient` instance **inside of your app, and on an instance ref (or in React state). This ensures that data is not shared between different users and requests, while still only creating the QueryClient once per component lifecycle.**
@@ -111,6 +181,95 @@ function Posts() {
111181

112182
As demonstrated, it's fine to prefetch some queries and let others fetch on the queryClient. This means you can control what content server renders or not by adding or removing `prefetchQuery` for a specific query.
113183

184+
#### With experimental `app` directory
185+
Wrap the `<QueryClientProvier>` and `<Hydrate>` components inside a Client Component that initializes the `QueryClient` your app will use on the client.
186+
187+
- `QueryClient` should be created on an instance ref (or in React State ). **This ensures that data is not shared between different users and requests, while still only creating the QueryClient once per component lifecycle.**
188+
- The `dehydratedState` prop will contain your prefetched data along with `dataUpdatedAt` which provides the time the data was fetched on the server
189+
190+
```tsx
191+
// app/react-query.jsx
192+
'use client'
193+
194+
import { QueryClient, QueryClientProvider, Hydrate } from '@tanstack/react-query'
195+
196+
export default function ReactQuery({dehydratedState, children}) {
197+
const [queryClient] = React.useState(() => new QueryClient())
198+
199+
return (
200+
<QueryClientProvider client={queryClient}>
201+
<Hydrate state={dehydratedState}>
202+
{children}
203+
</Hydrate>
204+
</QueryClientProvider>
205+
)
206+
}
207+
```
208+
209+
Prefetch your data inside a Server Component and provide your previously defined Client Component with the dehydrated state of the server-side `QueryClient`.
210+
211+
- Create a new QueryClient instance **for each page request. This ensures that data is not shared between users and requests.**
212+
- Prefetch the data using the clients prefetchQuery method and wait for it to complete
213+
- Use dehydrate to dehydrate the query cache and pass it to your wrapped client provider via the dehydratedState prop
214+
- Wrap your component tree with this new Server Component provider
215+
216+
```tsx
217+
// app/providers.jsx
218+
import { QueryClient, dehydrate } from '@tanstack/query-core'
219+
import ReactQuery from './react-query'
220+
221+
export default async function Providers({children}) {
222+
const queryClient = new QueryClient()
223+
await queryClient.prefetchQuery(['posts'], getPosts)
224+
225+
return (
226+
<ReactQuery dehydratedState={dehydrate(queryClient)}>
227+
{children}
228+
</ReactQuery>
229+
)
230+
}
231+
```
232+
```tsx
233+
// app/layout.jsx
234+
import Providers from './providers'
235+
236+
export default function RootLayout({children}) {
237+
return (
238+
<html>
239+
<head></head>
240+
<body>
241+
{
242+
// if using TypeScript, disable typechecking for Server Component returning Promise type
243+
// @ts-expect-error Server Component
244+
<Provider>{children}</Provider>
245+
}
246+
</body>
247+
</html>
248+
)
249+
}
250+
```
251+
252+
Prefetched data will now be available to components calling `useQuery`. It's fine to prefetch some queries on the server and let others fetch on the client.
253+
254+
```tsx
255+
// app/posts.jsx
256+
'use client'
257+
258+
import { useQuery } from '@tanstack/react-query'
259+
260+
function Posts() {
261+
// This useQuery could just as well happen in some deeper child to
262+
// the "Posts"-page, data will be available immediately either way
263+
const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })
264+
265+
// This query was not prefetched on the server and will not start
266+
// fetching until on the client, both patterns are fine to mix
267+
const { data: otherData } = useQuery({ queryKey: ['posts-2'], queryFn: getPosts })
268+
269+
// ...
270+
}
271+
```
272+
114273
### Caveat for Next.js rewrites
115274

116275
There's a catch if you're using [Next.js' rewrites feature](https://nextjs.org/docs/api-reference/next.config.js/rewrites) together with [Automatic Static Optimization](https://nextjs.org/docs/advanced-features/automatic-static-optimization) or `getStaticProps`: It will cause a second hydration by React Query. That's because [Next.js needs to ensure that they parse the rewrites](https://nextjs.org/docs/api-reference/next.config.js/rewrites#rewrite-parameters) on the client and collect any params after hydration so that they can be provided in `router.query`.

0 commit comments

Comments
 (0)