Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper way to use with Next SSR caching - Always seeing loading state / data not caching #291

Open
sgup opened this issue Apr 20, 2024 · 6 comments

Comments

@sgup
Copy link

sgup commented Apr 20, 2024

Hi, first of all thanks for this amazing library.

I'm trying to figure out the proper use for rendering & caching with SSR/Next. I'm using fetchOptions: { cache: 'force-cache' } in the SSR Apollo Wrapper. However I'm still seeing my graphql server being hit for every request, and it always shows the loading page for a second or if i disable JS (testing on the deploy).

Folder structure:

- app/blog/[id]/page.tsx
  - server component (no 'use client')
  - uses an apollo RSC client here for generateMetadata (no fetchOptions defined)
  - return <BlogPage id={id} />
  - export const revalidate = 86400;

- app/blog/[id]/loading.tsx
  - server component
  - loading screen

- app/blog/[id]/BlogPage.tsx
  - client component 'use client'
  - const { data } = useSuspenseQuery(GetGuideDocument, {
      variables: {
        guideId,
      },
      fetchPolicy: 'cache-and-network',
      context: {
        fetchOptions: {
          cache: 'force-cache',
        },
      },
    });

Full SSR:

'use client';
declare module '@apollo/client' {
  export interface DefaultContext {
    token?: string;
  }
}

import { ApolloLink, HttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import {
  ApolloNextAppProvider,
  NextSSRInMemoryCache,
  NextSSRApolloClient,
  SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support/ssr';
import typePolicies from './typePolicies';

const uri = `${process.env.NEXT_PUBLIC_GRAPHQL_URL}/graphql`;

// have a function to create a client for you
function makeClient() {
  const authLink = setContext(async (_, { headers, token }) => {
    return {
      headers: {
        ...headers,
        ...(token ? { authorization: `Bearer ${token}` } : {}),
      },
    };
  });

  const httpLink = new HttpLink({
    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri,
    // you can disable result caching here if you want to
    // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
    fetchOptions: { cache: 'force-cache' },
  });

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache({
      typePolicies,
    }),
    link:
      typeof window === 'undefined'
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            authLink.concat(httpLink),
          ])
        : authLink.concat(httpLink),
  });
}

// you need to create a component to wrap your app in
export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

Full RSC

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc';

const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      uri: process.env.NEXT_PUBLIC_GRAPHQL_URL + '/graphql',
      // you can disable result caching here if you want to
      // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
      // fetchOptions: { cache: "no-store" },
    }),
  });
});

export default getClient;

Thanks, any help much appreciated.

@sgup sgup changed the title Proper way to use with Next SSR caching Proper way to use with Next SSR caching - Always seeing loading state / data not caching Apr 20, 2024
@phryneas
Copy link
Member

This looks to me like you're forwarding the option the Next.js fetch cache correctly.
Unfortunately, beyond that it's more of a Next.js question and I don't really know how you could best debug this, I'm sorry.

@sgup
Copy link
Author

sgup commented Apr 29, 2024

Would there be a way to do the initial fetch using RSC (which seems to respect nextjs caching), passing the data to the client component, and then "patching-in" that cache into the client-side apollo client?

Essentially a way to auto-load the rsc-client cache into the browser apollo client cache would be amazing.

@sgup
Copy link
Author

sgup commented Apr 29, 2024

I seem to have answered my own question. Using this approach Apollo Docs: Rehydrating the client-side cache, and passing in the RSC Client's cache into the <ApolloWrapper> from the SSR Client (can maybe change it to a regular client?) in app/layout.tsx.

Usage:

  • Fetching in page.tsx's using rsc client, and using useQuery in the client components.
  • Passing in the rsc data into my client components, and falling back to it if client-side data (from the useQuery hook) not loaded yet.

@phryneas
Copy link
Member

phryneas commented May 2, 2024

Please don't do that, that cannot work correctly in all situations and is not supported!
The App router runs in streaming SSR, worst case RSC, SSR and the browser run simulateneously, it's even possible that you'd end up starting the same request mutliple times or overriding data in the browser.
We will soon ship support for that in this package, please don't try this before the next version (see #258).

The documentation you linked to is only valid for non-streaming renderToString SSR.

@sgup
Copy link
Author

sgup commented May 4, 2024

I see, ok! looking forward to the new support, thank you!

@sgup
Copy link
Author

sgup commented May 4, 2024

Actually i think what im doing right now isn't causing multiple concurrent queries, since I'm forcing static on the pages.

// page.tsc
// fetching data here with rscClient to cache for 24 hours, renders the page initially instantly without any visible loading state.
export const dynamic = 'force-static';
export const revalidate = 86400; // 24 hours

and then bootstrapping this client's cache into the browser cache, but doing a fresh query on the clientside to update the data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants