48
How to combine SSR and pagination with react-query
If you read my latest post about pagination with react query, you might have noticed that everything was client-side rendered. That's fine for some cases, but in others, you might require server-side rendering for better speed or SEO. Today, I want to adapt the code we built last time to set up a server-side rendered pagination with Next.js and react-query:
To not bore you with a new project setup, we will just modify the code from the previous article I wrote. Go ahead and clone the repository: you can inspect the finished code inside the PaginationSSR.js file in the pages directory or you copy the code from PaginationCSR.js inside a new page and follow along.
As detailed in the react-query docs on SSR, there are two ways of passing data into your page:
This is very easy: We just fetch the needed data on the server-side and give it to react-query as initalData and we are all set. There are some disadvantages though:
The mentioned issues are avoided using hydration, but the setup is a little more complex. However, I want to provide you with a solution that is bulletproof and production-ready, so we will go with option b.
import "../styles/globals.css";
import { ReactQueryDevtools } from "react-query/devtools";
import { Hydrate, QueryClient, QueryClientProvider } from "react-query";
import { useState } from "react";
function MyApp({ Component, pageProps }) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools>
</Hydrate>
</QueryClientProvider>
);
}
export default MyApp;
export async function getServerSideProps(context) {
let page = 1;
if (context.query.page) {
page = parseInt(context.query.page);
}
const queryClient = new QueryClient();
await queryClient.prefetchQuery(
["characters", page],
async () =>
await fetch(
`https://rickandmortyapi.com/api/character/?page=${page}`
).then((result) => result.json()),
);
return { props: { dehydratedState: dehydrate(queryClient) } };
}
localhost:3001/paginationSSR?page=14
to go directly to page 14 for example, will also fetch the data for page 1. This happens because our default value for page is set to 1, so it fetches the data for page 1 immediately after rendering. We will fix it like so:
const [page, setPage] = useState(parseInt(router.query.page) || 1);
now you can delete the useEffect hook. Since this page is server-side rendered, it has access to the page parameter immediately.
function handlePaginationChange(e, value) {
setPage(value);
router.push(`paginationSSR/?page=${value}`, undefined, { shallow: true });
}
refetchonMount
and refetchOnWindowFocus
to false. You will have to evaluate your use case to see whether it's best to leave them activated.
const { data } = useQuery(
["characters", page],
async () =>
await fetch(
`https://rickandmortyapi.com/api/character/?page=${page}`
).then((result) => result.json()),
{
keepPreviousData: true,
refetchOnMount: false,
refetchOnWindowFocus: false,
}
);
That's it for today. Feel free to drop any questions in the comments section and have an amazing week!
48