Skip to content

Instantly share code, notes, and snippets.

@ssbb
Last active November 5, 2022 21:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssbb/a71c43d34c8dca5fbc4971ef5d643613 to your computer and use it in GitHub Desktop.
Save ssbb/a71c43d34c8dca5fbc4971ef5d643613 to your computer and use it in GitHub Desktop.
Next.js graphql-request example
import { GraphQLClient } from 'graphql-request';
type RequestCache =
| 'default'
| 'no-store'
| 'reload'
| 'no-cache'
| 'force-cache'
| 'only-if-cached';
type Options = {
cache?: RequestCache;
revalidate?: number;
};
export const graphqlClient = (options: Options = {}): GraphQLClient => {
const { cache = 'default', revalidate } = options;
return new GraphQLClient('http://localhost:4000/graphql', {
fetch: (input: RequestInfo | URL, init?: RequestInit) => {
return fetch(input, { ...init, next: { revalidate: revalidate } });
},
cache: cache,
});
};
import { cache } from 'react';
import { graphqlClient } from "lib/graphql-client";
export const myFetch = cache(async () => {
const { myDocument } = await graphqlClient().request(myDocument, {});
});
@eric-burel
Copy link

eric-burel commented Nov 3, 2022

I feel like you'd rather want to cache the graphQL client, not the request result
Otherwise it works only if the request is strictly exactly the same

Is this cache reinitialized for each request? It's not very clear to me when it's created/filled/invalidated etc.

The thing is that in GraphQL, caching is not based on URL or full result, but based on the body of the request: you want to cache entities that are fetched multiple times, based on their id + be able to merge the fields if query B get more fields than query A for instance.

The GraphQL client does this for you hopefully but you need a way to reuse the same client for the whole request.

There are probably nice things to do around this "cache" function.

@ssbb
Copy link
Author

ssbb commented Nov 3, 2022

@eric-burel If I got it right - this cache functions just memoizes function within single request-response cycle.

So eg you can reuse you fetcher func inside multiple components or head.ts etc without query duplication. So it's more about deduping. For caching there is cache option for fetch + revalidate.

https://beta.nextjs.org/docs/data-fetching/caching#per-request-caching
https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#caveat-data-requests-must-be-cached-between-replays

For sure it's not something even near apollo caching which able to to deep-merge results based on node id's

@ssbb
Copy link
Author

ssbb commented Nov 3, 2022

I can’t get it work with Apollo or urql because it requires contexts even in ssr mode (looks like you can execute raw queries but I don’t think cache works then)

maybe you got it working?

my thoughts was to wrap this client into something and use it until there is first class support from some advanced graphql framework like Apollo or urql and then just change wrapper function

@eric-burel
Copy link

eric-burel commented Nov 3, 2022

Cache should work for raw query without a context when Apollo is used server-side, this pattern was already used with getServerSideProps: init an Apollo client, run a bunch of queries, pass the serialized cache as props, and then init the context in the Page component (in new terminology, the page being a client component, and getSSR being a top-level server component or layout basically)

With server component, it should be much simpler: init an Apollo client with its cache, fire queries, and that's it. However you need to make sure that all your server component uses the same client (= same cache) and that it's regenerated for each request (otherwise you cache data for different users...).

So I am pretty sure you don't need a context server-side, but only client-side. You might need to pass the resulting cache to a top-level client component if you want the client to be also "warm".

This is how I understand it, hope it can help you.

(edit: I haven't actually tried coding so sorry if my feedback is a bit abstract, won't have a chance to test that until a few weeks)

This means not using "useQuery" in server components, or any hook, which is not necessary for server calls, but directly "await client.query(...)". The hooks require the context, that may be your issue.

@ssbb
Copy link
Author

ssbb commented Nov 3, 2022

So I am pretty sure you don't need a context server-side, but only client-side. You might need to pass the resulting cache to a top-level client component if you want the client to be also "warm".

Yeah looks like you are right. Just tried it now and it works with apollo client I used for getStaticProps.

But there is some things I can't get (sorry). For Apollo you pass fetch options during client initializations.. meanwhile Next now using it on query-level to determine if page/component is static/dynamic or need revalidation etc. So I don't see a way to combine it.

So previously you don't care about such things too much - you tell nextjs to revalidate whole page at some interval and Apollo resolve cache within this page as well. Now looks like it conflicting. Or I miss something?

@ssbb
Copy link
Author

ssbb commented Nov 3, 2022

What I posted as a gist is more like to use what nextjs offers as a cache (ISR) + dedup (so you can run the same query during page generation) while don't care about graphql node-level caching.

@eric-burel
Copy link

But there is some things I can't get (sorry). For Apollo you pass fetch options during client initializations.. meanwhile Next now using it on query-level to determine if page/component is static/dynamic or need revalidation etc. So I don't see a way to combine it.

I think you would need to use a page-level API instead, there are ways to force the page to be dynamic or not. You would use a "normal" fetch. We will need to study the internals of Next.js a bit more to understand what this "fetch" thing is a bout and how it translates to GraphQL, I don't think there is an immediate answer, you need to reproduce Next.js features here more than just "using" them.

@ssbb
Copy link
Author

ssbb commented Nov 4, 2022

But there is some things I can't get (sorry). For Apollo you pass fetch options during client initializations.. meanwhile Next now using it on query-level to determine if page/component is static/dynamic or need revalidation etc. So I don't see a way to combine it.

I think you would need to use a page-level API instead, there are ways to force the page to be dynamic or not. You would use a "normal" fetch. We will need to study the internals of Next.js a bit more to understand what this "fetch" thing is a bout and how it translates to GraphQL, I don't think there is an immediate answer, you need to reproduce Next.js features here more than just "using" them.

So it's basically what I am doing here - caching on nextjs level but totally ignoring graphql node-level cache. It works fine and actually generating static pages or dynamic queries based on cache/revalidate param.

@ssbb
Copy link
Author

ssbb commented Nov 4, 2022

Also regarding node-level caching (something like what apollo/urql offers) - I don't think there a way to add something into request/response cycle on Next.js currently (something like entry.server.ts in remix).

So really what I posted is just what remix-graphql you liked doing (it does not support graphql caching as well).

@eric-burel
Copy link

Makes sense and it should works perfect, especially if you manage to keep requests at page-level.

Speaking of Remix, it reminds me a bit of this article: https://sergiodxa.com/articles/dependency-injection-in-remix-loaders-and-actions

This article shows how you can pass objects in Remix to inject a database connection to loaders, you end up with a "context" object in the action arguments.

In Next 13 this would translate to a "context" props for pages.

I expect Next.js to bring an API somehow like a server context builder, as you do in Apollo server for instance to build the GraphQL context. Processing a GraphQL request server-side is actually a problem very close to parsing the React tree to trigger "useQuery" in each component.

Database connection is actually a bad example because it's shared between all requests, but in this example nothing prevents the developer to also generate and inject request specific objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment