Bernardo Lopes
Bernardo Lopes

Reputation: 41

React-query + Nextjs using hydration config for SSR doesn't use cached results and always have to wait fetch again

I'm trying to use react-query on my nextjs app with SSR/SSG and tried a lot of tutorials. Took hydration config because it seems better than initialData approach.

Following react-query guide: https://react-query.tanstack.com/guides/ssr you just have to wrap the app on QueryClientProvider and Hydrate components passing a prop to each one.

    const App = ({ Component, pageProps }: AppProps) => {
      const [queryClient] = useState(
        () => new QueryClient({ defaultOptions: { queries: { staleTime: 30 * 1000 } } })
      )

      return (
        <QueryClientProvider client={queryClient}>
          <Hydrate state={pageProps.dehydratedState}>
            <ReactQueryDevtools initialIsOpen={false} />
            <ThemeProvider theme={theme}>
              <CssBaseline />
              <Component {...pageProps} />
            </ThemeProvider>
          </Hydrate>
        </QueryClientProvider>
      )
    }

    export default App

To start fetching data with react-query on getServerSideProps/getStaticProps you just instance a new queryClient, execute prefetchQuery and set a prop with dehydrate(queryClient).

    export async function getStaticProps() {
      const queryClient = new QueryClient()
      await queryClient.prefetchQuery<Todo[]>('todos', fetchTodos)

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

    const Home: NextPage = () => {
      const { data, isLoading } = useQuery<Todo[]>('todos', fetchTodos)
      console.log(isLoading)

      if (isLoading) return <h1>Loading...</h1>

I've created a small api that return same data as https://jsonplaceholder.typicode.com/todos but with a setTimeout of 3 seconds. The problem is that everytime i reload or move from one page to another using next/link, it takes 3 seconds to load the page. It indicates that react-query cache is not being used.

Tried moving the queryClient instance outside getServerSideProps.

Tried adding staleTime to all queries as my config shows above.

Only solution I've found was:

    export const queryClient = new QueryClient()

    export async function getStaticProps() {
      if (!queryClient.getQueryData('todos')) {
        await queryClient.prefetchQuery<Todo[]>('todos', fetchTodos)
      }

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

But there is no one talking about it and feels like i'm doing it wrong.

This is my network tab when moving to another page and going back before 30 seconds of my staleTime config and queryClient instanced outside gssp: enter image description here

Upvotes: 4

Views: 7157

Answers (2)

Ezequiel Cavallo
Ezequiel Cavallo

Reputation: 1

I was looking for a solution for myself to this problem and I found this work around. It was mentioned in this post. Basically, it check where the request come from. If it's from a client side request, getServerSideProps will return an empty object.

export const hasNavigationCSR = (next) => async (ctx) => {
  if (ctx.req.url?.startsWith('/_next')) {
    return {
      props: {},
    };
  }
  return next?.(ctx);
};

Then,

export const getServerSideProps = hasNavigationCSR(async (ctx) => {
  const queryClient = new QueryClient()
  if (!queryClient.getQueryData('todos')) {
    await queryClient.prefetchQuery<Todo[]>('todos', fetchTodos)
  }

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

Upvotes: 0

TkDodo
TkDodo

Reputation: 28843

if you're creating the queryClient outside of getStaticProps, you're sharing it between requests and users. That might be what you want, but it also might not be what you want. I am not sure what the lifetime of a QueryClient is that gets created outside of getStaticProps though.

Instead of doing the if check, you can also pass a staleTime to prefetchQuery. If data exists in the cache, it will be returned, otherwise, new data will be fetched:

await queryClient.prefetchQuery<Todo[]>('todos', fetchTodos, { staleTime: 1000 * 60 * 10 })

There is also the idea to newly create the cache inside getStaticProps / getServerSideProps, but prefill it with data from an external store, e.g. a redis cache. Have a look at this discussion: https://github.com/tannerlinsley/react-query/discussions/3563

Upvotes: 4

Related Questions