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

Suspense Query Returns Data but Triggers Forbidden Error in Next.js 14 #367

Closed
naimulemon opened this issue Oct 6, 2024 · 5 comments
Closed

Comments

@naimulemon
Copy link

Issue Description

When using Apollo Client's SuspenseQuery in a Next.js 14 project, the query successfully fetches and logs the data in the console, but it also throws a "Forbidden resource" error. The same query works perfectly with the standard useQuery, indicating that the issue arises specifically when using the Suspense mode.

This problem prevents proper error handling, as the error is emitted even when valid data is received. The error handling behavior appears inconsistent, and there’s no clear way to manage or silence the error when using the useSuspenseQuery.

Suspense Query Code:

const { data, error } = useGetUsersSuspenseQuery({
  variables: {
    searchInput: {
      page: Number(searchParams.get("page")) || 1,
      search: debouncedValue,
    },
  },
  fetchPolicy: "network-only",
  errorPolicy: "none",
});

Despite data being logged successfully, the SuspenseQuery still triggers a runtime error:

Unhandled Runtime Error
Error: Forbidden resource

This error occurs when rendering the component within the Suspense boundary. When switching to the standard useQuery, no such error occurs, and data is returned without issue.

Apollo Client Setup:

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  SSRMultipartLink,
  InMemoryCache,
  ApolloClient,
} from "@apollo/experimental-nextjs-app-support";

function makeClient() {
  const httpLink = new HttpLink({
    uri: "http://localhost:8080/graphql",
    credentials: "include",
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    credentials: "include",
    connectToDevTools: true,
    defaultOptions: {
      query: {
        fetchPolicy: "network-only",
        notifyOnNetworkStatusChange: true,
      },
      mutate: {
        fetchPolicy: "network-only",
      },
    },
    link: 
      typeof window === "undefined"
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

Steps to Reproduce

  1. Set up a Next.js 14 project with Apollo Client for GraphQL queries.
  2. Create a GraphQL query and use useGetUsersSuspenseQuery to fetch data in a component.
  3. Wrap the component in a <Suspense> and <ErrorBoundary> in the page layout.
  4. Ensure that the server uses cookies for authentication, and credentials: "include" is set in the Apollo Client configuration.
  5. Attempt to load the page. The console will log the data, but a 403 Forbidden error will also be thrown.

Expected Behavior

The SuspenseQuery should correctly fetch and render the data without triggering a forbidden error, especially when the data is already successfully retrieved. Error handling should allow more control over dealing with the error (if any).

Actual Behavior

The SuspenseQuery fetches data and logs it in the console, but simultaneously throws a runtime error (403 Forbidden), causing the query to break in Suspense mode. There's no way to handle this error gracefully, even though the data is valid.

Environment

  • Next.js Version: 14
  • Apollo Client Version: @apollo/client 3.x
  • GraphQL Server: Apollo Server running on http://localhost:8080/graphql
  • Browser: Chrome
  • Node.js Version: 18.x

Additional Notes

  • The issue appears to be unique to the Suspense mode of Apollo queries, as the same query works flawlessly with useQuery.
  • Error handling in Suspense queries should provide more flexibility, especially when valid data is already returned.

Link to Reproduction

https://github.com/naimulemon/Zuricart-official

Reproduction Steps

Steps to Reproduce

  1. Set up a Next.js 14 project with Apollo Client for GraphQL queries.
  2. Create a GraphQL query and use useGetUsersSuspenseQuery to fetch data in a component.
  3. Wrap the component in a <Suspense> and <ErrorBoundary> in the page layout.
  4. Ensure that the server uses cookies for authentication, and credentials: "include" is set in the Apollo Client configuration.
  5. Attempt to load the page. The console will log the data, but a 403 Forbidden error will also be thrown.

@apollo/client version

3.10.6

@phryneas
Copy link
Member

phryneas commented Oct 7, 2024

Hi @naimulemon! 👋

Are you doing any additional steps to ensure that the SSR of your Client Components has access to the cookie?
If this request happens on the server, the server has no way of knowing which cookies your browser would send to a different GraphQL server.

@phryneas phryneas transferred this issue from apollographql/apollo-client Oct 7, 2024
@naimulemon
Copy link
Author

@phryneas What are the additional steps you talk about? This is my root layout

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  SSRMultipartLink,
  InMemoryCache,
  ApolloClient,
} from "@apollo/experimental-nextjs-app-support";
import { cookies } from "next/headers";

// Modify the function to accept cookies
function getClientWithCookies(cookieString: string) {
  const httpLink = new HttpLink({
    uri: "http://localhost:8080/graphql",
    credentials: "include",
    headers: {
      cookie: cookieString, // Set cookies in headers
    },
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    credentials: "include",
    connectToDevTools: true,
    defaultOptions: {
      query: {
        fetchPolicy: "network-only",
        notifyOnNetworkStatusChange: true,
      },
      mutate: {
        fetchPolicy: "network-only",
      },
    },
    link:
      typeof window === "undefined"
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const cookieStore = cookies();
  const client = getClientWithCookies(cookieStore.toString());
  return (
    <html lang="en">
      <body className="bg-gray-100">
        <NextSSRPlugin routerConfig={extractRouterConfig(ourFileRouter)} />
        <ApolloWrapper>
          <ApolloProvider client={client}>{children}</ApolloProvider>
        </ApolloWrapper>
      </body>
    </html>
  );
}

Here is my root layout

export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const cookieStore = cookies(); const client = getClientWithCookies(cookieStore.toString()); return ( <html lang='en'> <body className='bg-gray-100'> <NextSSRPlugin routerConfig={extractRouterConfig(ourFileRouter)} /> <ApolloWrapper> <ApolloProvider client={client}>{children}</ApolloProvider> </ApolloWrapper> </body> </html> ); }

Here is my client .

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

export const { getClient, PreloadQuery, query } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    credentials: "include",
    connectToDevTools: true,
    defaultOptions: {
      query: {
        fetchPolicy: "network-only",
        notifyOnNetworkStatusChange: true,
      },
    },
    link: new HttpLink({
      uri: "http://localhost:8080/graphql",
      credentials: "include",
      headers: {},
      fetch: async (uri, options) => {
        const res = await fetch(uri, {
          ...options,
          cache: "no-store",
        });
        return res;
      },
    }),
  });
});

export function getClientWithCookies(cookies: string) {
  const client = getClient();
  client.setLink(
    new HttpLink({
      uri: process.env.API_URL,
      credentials: "include",
      headers: {
        Cookie: cookies,
      },
    })
  );
  return client;
}

@phryneas
Copy link
Member

phryneas commented Oct 7, 2024

Unfortunately, Next.js fails to provide easy access to cookies during the SSR of Client Components, so you have to retrieve your cookie in a RSC, pass it (potentially encrypted) to your ApolloWrapper and then access it in the link there.

See this example: #281 (comment)

@naimulemon
Copy link
Author

@phryneas My code is working on server component and client component when I use hook. I only give me issue when I use suspense query. What about prefetch and how can I use that? Is there any example with prefetch query?

Copy link
Contributor

github-actions bot commented Oct 8, 2024

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.

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