Next.js Discord

Discord Forum

React Query + Next.js

Unanswered
West African Crocodile posted this in #help-forum
Open in Discord
West African CrocodileOP
I'm dividing my page into five distinct sections. When I use the revalidateTag function, it triggers a refetch for all five fetches, but I only want to revalidate a specific tag. To avoid this issue, I considered using React Query instead of Next.js since it handles revalidation more efficiently. However, I still want to fetch the data on the server side for better SEO and have it cached there. At the same time, when the data is revalidated on the client side, it should also be updated on the server. How can I achieve this?

17 Replies

New Guinea Freshwater Crocodile
React Query is great, but has nothing to do with server. I am struggling to understand what are you actually trying to achieve. If you plan to call your server API, RQ will fetch what Next serves and that may or may not be cached. RQ doesn't know anything about if tha server data is stale or not. On the other hand if you render your page on the server, it will be rebuilt on revalidation, but only requested tag will be refetched and if this is not happening I would double check that you have not set or you are not calling any additional auto revalidation.
@West African Crocodile It's still revalidating the entire page even though I only requested a specific tag.
that's a bug i believe because i had an issue like that before, try adding this (for some reason this worked for me back then):
revalidateTag("your-tag"); redirect("/your-route");

idk why but revalidateTag would purge the cache from the page even though i didn't requet that data again.
I ended up creating a little showcase because I wanted to get it in the Next.js GitHb issues and at the end i didn't lol maybe i was silly and i was misusing the APIS.

BTW I have all code together to showcase the whole thing for demo purposes only lol
It purged data from both users and todos any time i revalidated a tag without redirecting if more of 1 tagged caches were living the same page, to solve it i had to redirect after revalidating the tag... weird

export async function revalidateUsers() { revalidateTag('users') // redirect('/') }
@B33fb0n3 you can archive that by using `revalidateTag("your-tag")` and `unstable_cache`. One example unstable cache function looks like this: tsx const getCachedUser = unstable_cache( async (id) => getUser(id), ['my-app-user'] );
and remember the cache tag has to be part of the third argument passed to unstable_cache, in the { tags: ["your-tag"] }

const getCachedUser = unstable_cache(
    async () => {
      return { id: userId }
    },
    [userId], // add the user ID to the cache key
    {
      tags: ['users'],
      revalidate: 60,
    }
  )
If that's what you want you can fetch the data in the server for the first time and then pass it down to your client component and give it to useQuery hook in the "initialData" => {queryKey : [ ... ], queryFn: async () => { ... }, initialData: theDataFromTheRSC}
@luis_llanes It purged data from both users and todos any time i revalidated a tag without redirecting if more of 1 tagged caches were living the same page, to solve it i had to redirect after revalidating the tag... weird `export async function revalidateUsers() { revalidateTag('users') // redirect('/') }`
West African CrocodileOP
I tried the redirection method you suggested, but it actually made things worse by triggering the request twice instead of just once, along with the other requests.
@West African Crocodile I tried the redirection method you suggested, but it actually made things worse by triggering the request twice instead of just once, along with the other requests.
Like i said i solved it like that back in June 2024, but it's weird youre triggering it twice if a server action only makes one roundtrip
@West African Crocodile I tried the redirection method you suggested, but it actually made things worse by triggering the request twice instead of just once, along with the other requests.
you are right, when revalidating serverside data, another request will be made and send back to your in the same roundtrip. That's how server works. You need to make another request to get new data.

You can use react query to prevent that (as it's on the client then), but you still need some route handler to fetch the data
@luis_llanes If that's what you want you can fetch the data in the server for the first time and then pass it down to your client component and give it to useQuery hook in the "initialData" => {queryKey : [ ... ], queryFn: async () => { ... }, initialData: theDataFromTheRSC}
West African CrocodileOP
I still need to revalidate the initial data on the server, which means assigning a tag to it and using revalidateTag. However, this brings me back to the same issue of revalidating the entire page again.
@West African Crocodile I still need to revalidate the initial data on the server, which means assigning a tag to it and using revalidateTag. However, this brings me back to the same issue of revalidating the entire page again.
Are you caching your auth data? If yes: DONT do it! That's a security issue and sensitive information may leak.

Are you checking your auth inside your layout? If yes: DONT do it! That's a secuity issue as well and will leak data. You can read about it here: https://github.com/eric-burel/securing-rsc-layout-leak

Please answer both questions
@B33fb0n3 Are you caching your auth data? If yes: DONT do it! That's a security issue and sensitive information may leak. Are you checking your auth inside your layout? If yes: DONT do it! That's a secuity issue as well and will leak data. You can read about it here: https://github.com/eric-burel/securing-rsc-layout-leak Please answer both questions
West African CrocodileOP
No, I'm not doing either of them.

Let me explain my current setup:

I receive a sections request from the server, where true in the request assigns a Next.js tag of 'sections'. I then use React Query to handle pagination because I couldn't find an efficient way to implement it directly in Next.js while ensuring proper deletion and revalidation. The pagination is managed on the same page without using URL parameters.

After performing a mutation, such as deleting an item, I revalidate the queries associated with the 'sections' tag in React Query and trigger a server action to revalidate the 'sections' tag in Next.js.

An alternative approach I considered was removing Next.js tags entirely and fetching the data on the server using prefetchInfiniteQuery from React Query. However, this approach has a drawback—data is always fetched on page load, there's no caching, and even if caching is applied, it won’t be revalidated through client-side invalidations.

For now, I’m leaning towards using Next.js tags alongside React Query on the client while revalidating both React Query and Next.js tags. However, I’m concerned about generating unnecessary requests.

I want to optimize the number of requests, avoid revalidating the entire page, and maintain caching on both the server and client until I explicitly trigger a revalidation.

The following code snippet is just a small example to demonstrate the issue. However, the actual page is divided into multiple segments, not just a single segment for sections.

What do you think? Is there a better approach?
// page.tsx (Next.js + ReactQuery Approach)

const sectionData = await fetchSections(1, PAGINATION_PAGE_LIMIT, true);

const initialData = {
    pages: [sectionData], 
    pageParams: [1],
};

return (
    <Sections initialData={initialData} />
)


// page.tsx (React Query Only)

await queryClient.prefetchInfiniteQuery({
      queryKey: ['sections'],
      queryFn: ({ pageParam = 1 }) => {
        return fetchSections(pageParam, PAGINATION_PAGE_LIMIT);
      },
      getNextPageParam: (lastPage, pages) => getNextPage(lastPage, pages, PAGINATION_PAGE_LIMIT),
    });

    return (
         <Sections />
      )


// sections.tsx
  const {
    data,
    isLoading,
    isFetching,
    error,
    fetchNextPage,
    hasNextPage,
    refetch,
  } = useInfiniteQuery({
    queryKey: ['sections'],
    queryFn: ({ pageParam = 1 }) => {
      return fetchSections(pageParam, PAGINATION_PAGE_LIMIT); 
    },
    getNextPageParam: (lastPage, pages) =>
      getNextPage(lastPage, pages, paginationPageLimit),
    initialData: () => initialData, // (Nextjs + React Query Approach only)
    staleTime: 1000 * 60,
  });


// deleteSectionMutation

  const deleteMutation = useMutation({
    mutationFn: () => deleteSection(id),
    onSuccess: async () => {
      await queryClient.invalidateQueries(['sections']);
      await revalidateSections(); // server action to revalidate sections tag of nextjs (Nextjs + ReactQuery Approach only)
    },
  })
West African CrocodileOP
If the terminology isn't clear, you can think of sections as folders or any other type of data grouping.

@B33fb0n3