React Query v4 + SSR in Next JS
Tags: react, nextjs, reactquery, javascript
Published: 2022-07-20 16:17:00

SSR data fetching + caching mechanism is a bit tricky in next js.

In this article, we will learn how to improve initial load time via SSR and have a high speed client side navigation with the help of CSR and React Query.

We will create a blog app using the [JSON Placeholder API](https://jsonplaceholder.typicode.com/).

We are going to see just important sections here. To see the full source code checkout the [github repo](https://github.com/ArianHamdi/React-Query-NextJS-SSR). You can also check the [Live demo](https://react-query-next-js-ssr.vercel.app/) to get a better sight. The React Query devtools is available in this demo so you can check the cache flow.

## Table of contents
   * [1. Create a new project](#create)
   * [2. Setup Hydration](#hydartion)
   * [3. Prefetching and dehydrate data](#dehydrate)
   * [4. Shallow Routing](#shallow)
   * [5. with-CSR HOC](#with-csr)
   * [6. handle 404 status code](#404)
   * [7. Conclusion](#conclusion)
   * [8. Refrences](#refrences)



### 1. Create a new project <a name='create'></a>
First, create a nextjs project:

```bash
yarn create next-app blog-app

or

npx create-next-app blog-app
```

Let's install React Query and Axios:
```bash
yarn add @tanstack/react-query axios

or

npm install @tanstack/react-query axios
```

### 2. Setup Hydration <a name='hydartion'></a>
 Due to the [react query documents](https://tanstack.com/query/v4/docs/guides/ssr#using-hydration) we set up hydration in _app.js : 
 

```javascript
//pages/_app.js

import { useState } from 'react';
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from 'lib/react-query-config';

function MyApp({ Component, pageProps }) {

    // This ensures that data is not shared 
    // between different users and requests
    const [queryClient] = useState(() => new QueryClient(config))

    return (
        <QueryClientProvider client={queryClient}>
            // Hydrate query cache
            <Hydrate state={pageProps.dehydratedState}>
                <Component  {...pageProps} />
            </Hydrate>
        </QueryClientProvider>
    )

}

export default MyApp;
```
### 3. Prefetching and dehydrate data <a name='dehydrate'></a>
Before we continue note that In v3, React Query would cache query results for a default of 5 minutes, then manually garbage collect that data. This default was applied to server-side React Query as well. This lead to high memory consumption and hanging processes waiting for this manual garbage collection to complete. In v4, by default the server-side cacheTime is now set to Infinity effectively disabling manual garbage collection (the NodeJS process will clear everything once a request is complete).

Now we need to prefetch data and dehydrate queryClient in `getServerSideProps` method : 

```javascript
//pages/posts/[id].js

import { getPost } from 'api/posts';
import { dehydrate, QueryClient } from '@tanstack/react-query';

export const getServerSideProps = async (ctx) => {

    const { id } = ctx.params;

    const queryClient = new QueryClient()

    // prefetch data on the server
    await queryClient.fetchQuery(['post', id], () => getPost(id))

    return {
        props: {
            // dehydrate query cache
            dehydratedState: dehydrate(queryClient),
        },
    }
}

```
P.S : We used `fetchQuery` instead of `prefetchQuery`, because `prefetchQuery` doesn't throw any error or return any data. We will talk more about it in [6. handle 404 status code](#404).

From now on we can easily use this prefetched data in our page, without passing any data by props.

Just to be clear lets take a look at the implementation of `getPost` method and `usePost` hook : 

```javascript
//api/posts.js

import axios from 'lib/axios';

export const getPost = async id => {
    const { data } = await axios.get('/posts/' + id);
    return data;
}
```

```javascript
//hooks/api/posts.js

import { useQuery } from '@tanstack/react-query';
import * as api from 'api/posts';

export const usePost = (id) => {
    return useQuery(['post', id], () => api.getPost(id));
}
```

Now we can use this ``usePost`` hook to get post data.

```javascript
//pages/posts/[id].js

import { useRouter } from 'next/router';
import { usePost } from 'hooks/api/posts'
import Loader from 'components/Loader';
import Post from 'components/Post';
import Pagination from 'components/Pagination';

const PostPage = () => {

    const { query: { id } } = useRouter();

    const { data, isLoading } = usePost(id);

    if (isLoading) return <Loader />

    return (
        <>
            <Post id={data.id} title={data.title} body={data.body} />
            <Pagination id={id} />
        </>
    )
}


// getServerSideProps implementation ...
// We talked about it in section 2
```
### 4. Shallow Routing <a name='shallow'></a>

We want to manage our data fetching and caching mechanism just in the client so we need to use ``shallow = true`` prop in the Link component for navigating between post pages to prevent calling `getServerSideProps` each time. This means that `getServerSideProps` method will only call when the users directly hit the URL of the post and not in the client side navigation within the app. 

We have a Pagination component to navigate between pages, so we use ``shallow = true`` here :

```javascript
//components/Pagination.jsx

import Link from 'next/link';

function PaginationItem({ index }) {

    return (
        <Link className={itemClassName} href={'/posts/' + index} shallow={true}>
            {index}
        </Link>
    )
}

export default PaginationItem;
```

P.S : We used the new link component in nextjs v12.2 so we didn't need to use `<a>` tag here.

### 5. with-CSR HOC <a name='with-csr'></a>

At this time nextjs v12.2 shallow routing only works for URL changes in the current page. [nextjs shallow routing caveats](https://nextjs.org/docs/routing/shallow-routing#caveats)
this means if you navigate from `/posts/10` to `/posts/15` with `shallow = true` the `getServerSideProps` won't call but if you navigate from `/home` to `/posts/15` the `getServerSideProps` is called even you use shallow routing and this will fetch unnecessary data even if it's available in the cache.

I found a [work around](https://github.com/vercel/next.js/discussions/32243#discussioncomment-1767080) that checks if this request to `getServerSideProps` is a client side navigation request or not. If it was, then returns an empty object for props and prevents fetching data on the server.
we can't prevent calling `getServerSideProps`when navigating between different pages but we can prevent fetching unnecessary data in the `getServerSideProps`.

Here is withCSR HOC implementation : 

```javascript
//HOC/with-CSR.js

export const withCSR = (next) => async (ctx) => {
 
    // check is it a client side navigation 
    const isCSR = ctx.req.url?.startsWith('/_next');

    if (isCSR) {
        return {
            props: {},
        };
    }

    return next?.(ctx)
}
```

Now we should wrap our `getServerSideProps` with this HOC.

```javascript
//pages/posts/[id].js

import { getPost } from 'api/posts';
import { dehydrate, QueryClient } from '@tanstack/react-query';
import { withCSR } from 'HOC/with-CSR'

export const getServerSideProps = withCSR(async (ctx) => {

    const { id } = ctx.params;

    const queryClient = new QueryClient()

    await queryClient.fetchQuery(['post', id], () => getPost(id))

    return {
        props: {
            dehydratedState: dehydrate(queryClient),
        },
    }
})
```

If we navigate from different pages to post pages, `getServerSideProps` won't fetch any data and it just returns an empty object for props.

### 6. Handle 404 status code  <a name='404'></a>
While Next.js renders an error page if a post is not available, it doesn't actually respond with an error status code.

This means that while you can be viewing a 404 error, the page is actually responding with a 200 code. To search engines, this essentially translates to: "Everything went fine and we found the page". Rather than actually responding with a 404, which tells search engines that page doesn't exist.

To resolve this issue, let's take a look at `getServerSideProps` again : 
```javascript
const Page = ({ isError }) => {

    //show custom error component if there is an error
    if (isError) return <Error />

    return <PostPage />

}

export const getServerSideProps = withCSR(async (ctx) => {

    const { id } = ctx.params;

    const queryClient = new QueryClient();

    let isError = false;

    try {
        await queryClient.fetchQuery(['post', id], () => getPost(id));
    } catch (error) {
        isError = true
        ctx.res.statusCode = error.response.status;
    }

    return {
        props: {
            //also passing down isError state to show a custom error component.
            isError,
            dehydratedState: dehydrate(queryClient),
        },
    }
})

export default Page;
```


### 7. Conclusion <a name='conclusion'></a>

We setup a caching mechanism with the ability to prefetch data on the server in SSR context. We also learned how to use shallow routing for faster client side navigation.


Here is the [live demo](https://react-query-next-js-ssr.vercel.app/) of our implementation and the [github repository](https://github.com/ArianHamdi/React-Query-NextJS-SSR) for source code.
As well I had been added React Query devtools into production for you to understand thoroughly what is going under the hood.

**I would like to extend my sincere thanks to @aly3n.**

### 8. Refrences <a name='refrences'></a>
1. [JSON Placeholder API](https://jsonplaceholder.typicode.com/)
2. [React Query setup hydration](https://tanstack.com/query/v4/docs/guides/ssr#using-hydration)
3. [React Query no manual garbage collection server side](https://tanstack.com/query/v4/docs/guides/migrating-to-react-query-4#no-default-manual-garbage-collection-server-side)
4. [nextjs shallow routing caveats](https://nextjs.org/docs/routing/shallow-routing#caveats)
5. [Prevent data fetching in getServerSideProps on client-side navigation](https://github.com/vercel/next.js/discussions/32243#discussioncomment-1767080)
6. [respond with a 404 error in Next.js](https://ironeko.com/posts/how-to-return-a-404-error-in-getserversideprops-with-next-js)
7. [Project source code](https://github.com/ArianHamdi/React-Query-NextJS-SSR)
8. [Live demo](https://react-query-next-js-ssr.vercel.app/)