apollo-client-nextjs icon indicating copy to clipboard operation
apollo-client-nextjs copied to clipboard

Making sense of different layers of caching

Open devdumpling opened this issue 9 months ago • 4 comments

Hi there! Thanks for all your work on this (especially the RFC, blog posts, and discussions between you and Dan--immensely helpful to me). I've been doing a deep dive across this repo, the Next.js caching page, and various stack overflow posts trying to piece it all together.

I've been testing using this in my org's new frontend, and while I believe I've got it working, I'm still struggling to understand the different layers of caching, how these new Apollo Client instances fit into that, and ultimately how to structure queries.

The different layers

Going to do my best to explain based on my current understanding, feel free to correct.

In a standard Next.js 14 app, there are four caching layers (per their docs):

  1. React Request Memoization -- React's caching of GET fetch requests during the RSC render pass. Only caches for the lifetime of a server request during rendering. Can opt out with AbortController.
  2. Next Data Cache -- Next persistent data cache across incoming server requests. When people talk about the Next monkey patched fetch, this is what they're referring to. Can be opted out with no-store or unstable_noStore.
  3. Next Full Route Cache -- Next.js automatic static optimization. Caches static routes at build time. Caches the RSC Payload (HTML) for a static route. Can be used for non-interactive, immediate previews, then payload reconciles the client and hydrates the DOM. Opt out with route segment config (revalidate = 0 or dynamic = force-dynamic or if opted out of Next Data Cache already).
  4. Next Client Router Cache -- In-memory client-side cache that stores RSC payload for a user-session. Works with prefetching to help improve back/fwd navigation for visited routes to eliminate full-page reloads and maintain state.

Adding ApolloClient into the mix we now have:

  1. A request-scoped, RSC-only Apollo Client -- In-memory normalized Apollo cache that allows us to securely tie requests together during an RSC pass. Utilizes React.cache to share the instance for a single request. Seems to work with next/cookies in my limited testing. This works with the Next.js Data Cache as well.
  2. An SSR/CSR Apollo Client -- Traditional ApolloProvider wrapper, but with varying logic depending on whether it's running in the SSR pass or in the browser. This works with the Next.js Data Cache as well.

Questions

  1. How does the RSC-only Apollo Client play with the React Request Memoization? Do they work together or are they totally agnostic? Given that they're both dedup-ing GET requests, what is the advantage of having the AC one? Are there any risks? So far the biggest advantage my org has with AC is that it minifies our fragments (pulls out duplicate fragments in a large request where we're nesting a bunch of potentially duplicate fragments due to our monorepo setup).
  2. How should I be using the Apollo caching options, e.g. cache-first, cache-and-network, etc in this new world? Seems like there's overlap with the Next data cache. Do I need them at all?
  3. We're using @apollo/client/link/http's createHttpLink to build our HTTPLink instead of a new HTTPLink. We then use from to chain this with some other links for debugging, auth, etc like we'd do in any Apollo project. Is there any difference or best practice here?
  4. Are there plans to integrate this library into @apollo/client or do you all plan to keep it separate? Just curious. Having it separate right now actually helps us maintain some separation between old and new in our monorepo.

Again, thank you so much. Please let me know if there's a better format or place for these questions. I was going to post in Discord, but felt like it might be more readable/helpful to others here. I'm also generally curious around best practices for data fetching going forward like in this issue, since Next seems to recommend component-level fetching and relying on deduping, but this post is already so long I'll leave that out for now.

devdumpling avatar Nov 10 '23 13:11 devdumpling

Heyo, super confusing topic :)

How does the RSC-only Apollo Client play with the React Request Memoization? Do they work together or are they totally agnostic? Given that they're both dedup-ing GET requests, what is the advantage of having the AC one? Are there any risks? So far the biggest advantage my org has with AC is that it minifies our fragments (pulls out duplicate fragments in a large request where we're nesting a bunch of potentially duplicate fragments due to our monorepo setup).

Apollo Client adds AbortController to outgoing requests, so the React Request Memoization should be disabled. Funny that is currently in there, from what I know it was added for a while and then removed from React again.
In RSC you probably gain most from request deduplication (the same query will not cause two requests, I don't know how the React cache would handle that for requests started in parallel) and the ability to just select fragments in child components if a parent component did a bigger/composed request.

How should I be using the Apollo caching options, e.g. cache-first, cache-and-network, etc in this new world? Seems like there's overlap with the Next data cache. Do I need them at all?

Tbh., in my eyes only cache-first really makes sense here.

I have to admit that I don't particularly like the Next Data Cache since it caches APIs under the assumption that no other non-Next.js API consumer will ever cause any data writes there. (Even a Next.js instance hosted on a different host in manual hosting, but interacting with the same DB/API could break this assumption.)
There's a reason that request or database caching on the server is usually something that needs to be added manually after a lot of consideration and is not out of the box included in old-fashioned server frameworks like Symfony, Laravel or Nest, or we would be doing this for 1-2 decades already.

We're using @apollo/client/link/http's createHttpLink to build our HTTPLink instead of a new HTTPLink. We then use from to chain this with some other links for debugging, auth, etc like we'd do in any Apollo project. Is there any difference or best practice here?

HTTPLink and createHttpLink are the same thing, both will leave you with a ApolloLink instance that can be chained in the same way.

Are there plans to integrate this library into @apollo/client or do you all plan to keep it separate? Just curious. Having it separate right now actually helps us maintain some separation between old and new in our monorepo.

Once necessary APIs land in the React core (hopefully in React 19), we'd love to take the learnings from here and create a framework-agnostic solution as part of @apollo/client. As long as we have to fall back on Next.js internals, it will stay it's own package.

phryneas avatar Nov 10 '23 16:11 phryneas

Amazing, thank you for the detailed response! This is exactly the info I needed. 🙏

We do indeed manually host, so will be careful here.

Definitely like the idea of a framework-agnostic solution, hopefully that ends up being feasible! Feel free to close or leave this open if it's helpful for others.

devdumpling avatar Nov 10 '23 17:11 devdumpling

How would you do things like client.writeQuery etc to update the cache? https://www.apollographql.com/docs/react/caching/cache-interaction/

in my situation I'm dealing with a schema where different types represent the same underlying object (swagger wrapper) so i have to manually update the cache after mutating.

dpnova avatar Nov 28 '23 02:11 dpnova

@dpnova in the browser, you could just continue doing that. On the server, it probably doesn't make sense since you'd always have a new cache instance everytime anyways, so nothing to update.

phryneas avatar Nov 28 '23 11:11 phryneas