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
351 stars 25 forks source link

Server side request processing in next.js 14 #239

Closed iewher closed 1 month ago

iewher commented 1 month ago

Hi, I apologize in advance if I distracted you This is my first time having to render a request on the server side. I'm trying to get data from graphql, but I can't do it on the server side, which is very important for me, because metadata is used.

request:

import { useQuery } from "@apollo/experimental-nextjs-app-support/ssr"
import { gql } from "__generated__"

const GET_PROFILE_USER_QUERY = gql(
  `query getProfileUser($limit: Int!, $offset: Int!) {
    users(limit: $limit, offset: $offset) {
      count
      results {
        avatarId
        company
        createdAt
        description
        email
        fullName
        id
        isAdmin
        isVerified
        phoneNumber
        telegramUsername
        username
        viberPhoneNumber
        whatsappPhoneNumber
        roles 
      }
    }
  }`
)

usage:

const { data, loading } = useQuery(GET_PROFILE_USER_QUERY, { variables: { limit: 1024, offset: 10 } })

my ApolloNextProvider :

"use client"

import { HttpLink, ApolloLink } from "@apollo/client"
import { ReactNode } from "react"
import {
  ApolloNextAppProvider,
  NextSSRInMemoryCache,
  NextSSRApolloClient,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr"

function makeClient() {
  const httpLink = new HttpLink({
    uri: "/api/v2/query",
    fetchOptions: { cache: "no-store" },
  })

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

export const ApolloContext = ({ children }: { children: ReactNode }) => {
  return <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>
}

my layout:

export default async function RootLayout({ children }: { children: ReactNode }) {

  return (
  <html lang="ru" className={roboto.className}>
      <body>
          <ApolloContext>
              <div className={styles.Center}>
                <main>{children}</main>
              </div>
          </ApolloContext>
      </body>
    </html>
  )
}

I don't understand why it gives me this error изображение How can I tell if I'm trying to call a client function from the server? Got the information from your readme file Help pls

phryneas commented 1 month ago

You have to mark the file using useQuery as a Client Component file with "use client" at the top of the file.

Then it won't run in React Server Components, but only during SSR and in the Browser. Since useQuery does not run anything in SSR, your query would only happen in the browser.

iewher commented 1 month ago

I moved the client into a separate component

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

export const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      // this needs to be an absolute url, as relative urls cannot be used in SSR
      uri: "/api/v2/query",
      // 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" },
    }),
  });
});

then I try to call it like this:

  const { data: category } = await getClient().query({ query: GET_CATEGORY_QUERY })

  console.log(category)

my apollo provider

"use client"

import { ApolloProvider } from "@apollo/client"
import { ReactNode } from "react"
import { getClient } from "./client"

export const ApolloContext = ({ children }: { children: ReactNode }) => {
  const client = getClient()

  return <ApolloProvider client={client}>{children}</ApolloProvider>
}

error:

'server-only' cannot be imported from a Client Component module. It should only be used from a Server Component.

The error was caused by importing '@apollo/experimental-nextjs-app-support/dist/rsc/index.js' in './contexts/apollo/client.ts'.

why do I get an error if I export getClient not from a client component

phryneas commented 1 month ago

Oh, sorry, I misread your comment as "I don't want to do it on the server".

You can do so in React Server Components, but that means using getClient.query(), not the useQuery hook.

phryneas commented 1 month ago

Oh... now you're just mixing things up.

Please take a step back and stop doing what you do right now :)

phryneas commented 1 month ago
"use client"

import { ApolloProvider } from "@apollo/client"
import { ReactNode } from "react"
import { getClient } from "./client"

export const ApolloContext = ({ children }: { children: ReactNode }) => {
  const client = getClient()

  return <ApolloProvider client={client}>{children}</ApolloProvider>
}

This is not the way to use Apollo Client in Next.js.

You can use Apollo Client in two distinct ways in Next.js

That said, loking at your code snippets:

iewher commented 1 month ago

Thank you for sending links to documentation You wrote that I can use the Apollo client in two different ways, I did that, now my code looks like this

apolloContext.ts

"use client";

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

function makeClient() {
  const httpLink = new HttpLink({
    uri: "/api/v2/query",
    fetchOptions: { cache: "no-store" },
  });

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

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

and

client.ts

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

export const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      uri: "/api/v2/query/",    
    }),
  });
});

In the case of useQuery everything works fine, but in the case of getClient it doesn’t, here is the code and error

import { getClient } from "contexts/apollo/client"

export default async function CategoryPage({ params }: PageProps) {
  const { data } = await getClient().query({ query: GET_CATEGORIES_QUERY })

  console.log(data)
}
Unhandled Runtime Error

Error: Failed to parse URL from /api/v2/query

Why does one option work and the other not? Thank you for all your efforts

phryneas commented 1 month ago

When you're on a server, you cannot make a request to an url like /api/v2/query. The server doesn't know what that relative path would be relative to - there is no URL bar. Is it http://localhost:8000/api/v2/query? Is it https://example.com/api/v2/query?

You need to specify a full URL on the server.

iewher commented 1 month ago

You need to specify a full URL on the server.

In this case, why does useQuery work and return data normally?

jerelmiller commented 1 month ago

Hey @iewher 👋

Please see the comment above which explains why this works with useQuery during SSR (emphasis mine).

Using ApolloNextAppProvider and preferrably useSuspenseQuery instead of useQuery (useQuery will not run in SSR)

iewher commented 1 month ago

Thank you for your efforts, I figured it out, the issue can be closed

jerelmiller commented 1 month ago

Awesome to hear! Have a great day 🙂

github-actions[bot] commented 1 month 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.