apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
436 stars 32 forks source link

useSuspenseQuery: Server functions cannot be called on initial render. #355

Closed chris-snow closed 3 weeks ago

chris-snow commented 3 weeks ago

I've swapped out useQuery for useSuspenseQuery and I'm now getting the following error:

ApolloError: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.

This is happening (assumption noted) because I am calling a server function to retrieve my authorization token when creating my authLink for the client side apollo client:

const authLink = setContext(async (_, { headers }) => {
    const token = await getAuthToken(); <-- Server function

    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
      },
    };
  });

This error does not get shown when using useQuery, only when using useSuspenseQuery. I've attempted other methods of retrieving my token but unfortunately cannot reliably do this.

  1. Created a route handler to retrieve the token. This works for the client side, but fails on the initial server request due to the path not being absolute. I know I can work around this, but would prefer not to if possible.
  2. I'm unable to use 3rd party cookie libraries to read the token on the client side as it is httpOnly (which I'd prefer to keep as is for obvious reasons).
  3. I could pass the token in as a prop from the ApolloWrapper, but unfortunately I have no way to reliably ensure that the token is regularly updated (i.e. read once on mount, it will become stale after a period of time).

    I use PreloadQuery (as covered in the docs):

    Parent component:

    <PreloadQuery
        query={QueryDocumentDocument}
        variables={{
          take: 10,
          skip: 0,
        }}
      >
        <Suspense fallback={<ChildComponentLoading />}>
          <ChildComponent />
        </Suspense>
      </PreloadQuery>

    Child component:

    
    const { data } = useSuspenseQuery<Query, QueryVariables>(
    QueryDocument,
    {
      variables: {
        filter: {
          take: pageSize,
          skip: (currentPage - 1) * pageSize,
        },
      },
    },
    );


Any assistance would be greatly appreciated.
phryneas commented 3 weeks ago

Phew, this is a difficult one - unfortunately React seems to be more opinionated than practical here :/

This is made even more difficult by the fact that useSuspenseQuery will also run during SSR, so your server action will be executed from the server, but from outside your RSC.

For the SSR part, you could use something like https://github.com/phryneas/ssr-only-secrets, but that doesn't solve the problem at hand.

Passing your token down as a prop unencryped would expose it to the browser, which is generally a thing I'd try to avoid.

That said, maybe that's the core of your problem - calling your getAuthToken function from the browser makes your auth token available to userland JavaScript again, which is what you want to avoid with the httpOnly cookie in the first place, and it kinda ruins that layer of security.

Is there any way to establish that cookie directly between your browser and GraphQL server in the first place so you don't need that authentication header? That would be more secure and remove the need for the server action call.

chris-snow commented 3 weeks ago

Thanks for your response. The path I've decided to take, based on your suggestion, is altering my API so that it can accept either cookie or header based authentication.

The RSC client sends the header based auth, and the 'client' client sends credential based cookies. NextJS not making things easy but at least I have a workaround for the timebeing.

I still seem to be getting an unauthorized response on my very first RSC request, but I'll just have to live with that - I'm no longer getting the error I mentioned above.

Appreciate your help!

github-actions[bot] commented 3 weeks ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.