Running Next.js navigation through react-query

If you think about it, every client-side navigation in next.js page router awaits a GET request to complete. What if we could run those client-side fetch requests through a client-side fetch library like react-query? I've created a next-query-glue library that allows you to do it. That brings a lot of control over route data requests and greatly improves the user experience.

Next.js page router default behaviour

The Nextjs page router guarantees that route data is fresh on every navigation. As a result, users have to wait for the getServerSideProps response on every navigation. I made a simple demo website that imitates the behavior of the NextJS page router. Navigate between pages to see how it works.

Home

Articles:

Lorem ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vitae dui ultricies, tincidunt turpis eget, maximus elit. Cras molestie enim quis orci mattis varius. Quisque id rhoncus massa, ut laoreet nunc. Ut lorem ex, varius sed ullamcorper a, ultricies vitae urna. Aliquam blandit leo vitae fermentum eleifend. Vivamus varius a tortor ut interdum. Duis eu neque vestibulum, volutpat risus quis, accumsan nulla.

Client side caching layer (SWR + Invalidation)

With react-query and next-query-glue, pages can get a stream of route data that updates constantly and automatically. In that article I'll shortly describe some important properties of react-query. I highly recommend to read react-query documentation.

  • staleTime - the number of milliseconds that a data should be considered fresh when attempting to load (default is 0, recommended 1 minute or more for loading route data).
  • gcTime - the number of milliseconds that a data should be kept in the cache before being garbage collected (default is 5 minutes, recommended 5 minutes or more for loading route data).

Those properties reveal how automatic invalidation works in react-query. It's not recommended to change them.

  • refetchOnWindowFocus (by default is true) - revalidates query data once user reopens tab/window of your website.
  • refetchOnReconnect (by default is true) - revalidates query data once user regained a network connection.
  • refetchOnMount (by default is true) - revalidates query data once component calling useQuery is mounted.
Home

Articles:

Lorem ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vitae dui ultricies, tincidunt turpis eget, maximus elit. Cras molestie enim quis orci mattis varius. Quisque id rhoncus massa, ut laoreet nunc. Ut lorem ex, varius sed ullamcorper a, ultricies vitae urna. Aliquam blandit leo vitae fermentum eleifend. Vivamus varius a tortor ut interdum. Duis eu neque vestibulum, volutpat risus quis, accumsan nulla.

Thanks to react-query, navigation now is a non-blocking operation. The page view updates instantly. While isLoading flag from useQuery is equal to true, the page's skeleton layout appears. The effect is similar to loading.jsx file from the app router, but with react-query, you manage the loading state at the component level, while the app router handles it at the route view level.

Optimistic navigation via placeholderData prop

Here's a quote from react-query documentation about placeholderData property

Example: An individual blog post query could pull "preview" data from a parent list of blog posts that only include title and a small snippet of the post body. You would not want to persist this partial data to the query result of the individual query, but it is useful for showing the content layout as quickly as possible while the actual query finishes to fetch the entire object.

Optimistic navigation is a good alternative to prefetching. When the "Lorem ipsum" article is opening for the first time, it feels that it opens instantly because of optimistic UI.

Home

Articles:

Lorem ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vitae dui ultricies, tincidunt turpis eget, maximus elit. Cras molestie enim quis orci mattis varius. Quisque id rhoncus massa, ut laoreet nunc. Ut lorem ex, varius sed ullamcorper a, ultricies vitae urna. Aliquam blandit leo vitae fermentum eleifend. Vivamus varius a tortor ut interdum. Duis eu neque vestibulum, volutpat risus quis, accumsan nulla.

When the home page is loaded, its route data contains an array of articles; every article object contains a title, description, and thumbnail. We use that data to display a list of article cards, and we pass it as a placeholderData property in the useQuery hook. This explains why when the article is opened for the first time, its content displays a skeleton instead of actual data. The second and subsequent openings will utilize data from the cache.