vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.54k stars 26.82k forks source link

graphql-request and caching #66503

Open pepew-le-boss opened 4 months ago

pepew-le-boss commented 4 months ago

What is the update you wish to see?

The caching documentation is not clear on how to do caching with some clients like graphql-request.

Is there any context that might help us understand?

I'm using graphql-request in my NextJS 14 app and I have been using it for more than a year now without any problems. However, I now want to memoize the requests because on most of my pages, some requests are triggered multiple times even though they are the same (same URL, same params, same query, etc.).

I understand that this doesn't work out of the box because GraphQL uses POST requests, and they are not cached by default. According to the documentation: https://nextjs.org/docs/app/building-your-application/caching#react-cache-function, we need to use the cache() function from React. Perfect.

The problem is that when using the cache function, it still doesn't work. For example, doing something like this:

import { GraphQLClient } from "graphql-request"
import { cache } from "react"

export const client = new GraphQLClient("http://localhost:3000/api/graphql", {
  fetch: cache(async (url: URL | string | Request, init?: RequestInit) =>
    fetch(url, {
      ...init,
    }),
  ),
})

Why? Because the cache function needs to have only primitives as parameters (as stated in the React documentation https://react.dev/reference/react/cache#memoized-function-still-runs). If you look at the example above, url is a string so it's fine but init is an object and because React cache checks for shallow equality and not deep equality, the init object will be different each time the function is run, meaning that the caching will not work.

So how we should implement caching with graphql-request ?

PS: 2 issues about this subject but without solutions

Does the docs page already exist? Please link to it.

https://nextjs.org/docs/app/building-your-application/caching#react-cache-function

icyJoseph commented 4 months ago

Hi,

The cache function from React is scoped to the current request. By definition it won't work as you expect it (as a global cache)

You need to pass the next revalidate option:

fetch(`https://...`, { next: { revalidate: 3600 } })
pepew-le-boss commented 4 months ago

I'm not looking for a global cache like the NextJS Data Cache (what you're suggesting). I'm looking for request memoization like the React Cache.

icyJoseph commented 4 months ago

Aha, yeah I had missed the bit about shallow equality on the arguments passed to cache. Though you say:

Because the cache function needs to have only primitives as parameters

But the docs you link to say:

If your arguments are not primitives (ex. objects, functions, arrays), ensure you’re passing the same object reference.

So you can in turn, use cache to memoize the creation of the init argument, see https://github.com/vercel/next.js/discussions/63298#discussioncomment-8797531 - since you can also use the cache to store a computation, it is not limited to just requests.

pepew-le-boss commented 4 months ago

So you're suggesting to use a Map to store the init object inside so I get the same reference of the init object ?