Skip to content
New issue

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

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

Already on GitHub? # to your account

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

Closed
chris-snow opened this issue Aug 27, 2024 · 7 comments
Closed

Comments

@chris-snow
Copy link

chris-snow commented Aug 27, 2024

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
Copy link
Member

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
Copy link
Author

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!

Copy link

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.

@ikaasraa
Copy link

ikaasraa commented Nov 3, 2024

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!

I have this problem and I use Authorization with token in header can you send the code how you solve this issue please

@ikaasraa
Copy link

ikaasraa commented Nov 3, 2024

is there a approach to solve it ??

@ikaasraa
Copy link

ikaasraa commented Nov 4, 2024

you just need to await the prefetchedQuery in the Page, that clears the client error @ikaasraa

Please send some example

@jerelmiller
Copy link
Member

@withintheruins14 that looks like TanStack Query code, not Apollo Client code. Are you sure you're in the right place?

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

No branches or pull requests

4 participants