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

Server side request processing in next.js 14 #239

Closed
iewher opened this issue Mar 13, 2024 · 13 comments
Closed

Server side request processing in next.js 14 #239

iewher opened this issue Mar 13, 2024 · 13 comments

Comments

@iewher
Copy link

iewher commented Mar 13, 2024

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

@iewher iewher changed the title ssr in next js 14 Server side request processing in next.js 14 Mar 13, 2024
@phryneas
Copy link
Member

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

iewher commented Mar 14, 2024

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

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

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

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

@phryneas
Copy link
Member

phryneas commented Mar 14, 2024

"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

  • Server Components -> registerApolloClient -> getClient().query(...) - this approach does not use hooks documented here
  • Client Components (will run in SSR and in the Browser) -> Using ApolloNextAppProvider and preferrably useSuspenseQuery instead of useQuery (useQuery will not run in SSR). documented here This does not call the getClient from your Server Components!

That said, loking at your code snippets:

  • getClient is something to only call in React Server Components. You're using a RSC api in a Client Component here
  • ApolloProvider is something you should never directly use in Next.js

@iewher
Copy link
Author

iewher commented Mar 14, 2024

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

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

iewher commented Mar 14, 2024

You need to specify a full URL on the server.

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

@jerelmiller
Copy link
Member

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

iewher commented Mar 14, 2024

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

@jerelmiller
Copy link
Member

Awesome to hear! Have a great day 🙂

Copy link
Contributor

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.

@kunalvirk
Copy link

Hi,

That said, loking at your code snippets:

  • getClient is something to only call in React Server Components. You're using a RSC api in a Client Component here
  • ApolloProvider is something you should never directly use in Next.js

I do have similar apollo setup in my next.js client. But the getClient does not have the similar headers as ApolloNextAppProvider, which fails the query for 'Not Authorized' on server-side but works fine on client components.

Is it possible to set similar headers in both the instances?

Thanks,

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

4 participants