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

How do you use this properly with Next Auth? #44

Closed jnovak-SM2Dev closed 2 months ago

jnovak-SM2Dev commented 10 months ago

I'm trying to figure out how to use this properly with Next Auth. I put what I have below, it does work, but I don't think it's correct and the client side has to keep calling to get the token on each call, which seems wrong. Any suggestions or samples would be incredibly helpful.

layout.tsx

import { ApolloWrapper } from "../lib/apolloProvider";
import "./globals.css";
import { Inter } from "next/font/google";
import { NextAuthProvider } from "./providers";
import { getSession } from "next-auth/react";
import { NextSessionProvider } from "./nextsessionprovider";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <NextAuthProvider>
          <NextSessionProvider>
            <header>
              My Website
              <nav></nav>
            </header>
            <main>{children}</main>
          </NextSessionProvider>
        </NextAuthProvider>
      </body>
    </html>
  );
}

NextAuthProvider

"use client";

import { SessionProvider } from "next-auth/react";

type Props = {
  children?: React.ReactNode;
};

export const NextAuthProvider = ({ children }: Props) => {
  return <SessionProvider>{children}</SessionProvider>;
};

NextSessionProvider

import { SessionProvider, getSession } from "next-auth/react";
import { ApolloWrapper } from "../lib/apolloProvider";
import { getServerSession } from "next-auth";

type Props = {
  children?: React.ReactNode;
};

export const NextSessionProvider = async ({ children }: Props) => {
  return <ApolloWrapper>{children}</ApolloWrapper>;
};

ApolloWrapper

"use client";

import {
  ApolloClient,
  ApolloLink,
  HttpLink,
  SuspenseCache,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import {
  ApolloNextAppProvider,
  NextSSRApolloClient,
  NextSSRInMemoryCache,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { getSession } from "next-auth/react";
import { PropsWithChildren, useCallback } from "react";

const authLink = setContext(async (_, { headers }) => {
  const session = await getSession();
  const modifiedHeader = {
    headers: {
      ...headers,
      authorization: session?.jwt
        ? `Bearer ${session.jwt}`
        : `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
    },
  };
  return modifiedHeader;
});

function makeClient() {
  const httpLink = new HttpLink({
    uri: process.env.NEXT_PUBLIC_API_URL,
    fetchOptions: { cache: "no-store" },
  });

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

function makeSuspenseCache() {
  return new SuspenseCache();
}

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

client.ts

import {
  ApolloClient,
  HttpLink,
  InMemoryCache,
  createHttpLink,
  from,
} from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
import { getSession } from "next-auth/react";
import { setContext } from "@apollo/client/link/context";
import { getServerSession } from "next-auth";
import { authOptions } from "../app/authOptions";

const httpLink = createHttpLink({
  uri: process.env.NEXT_PUBLIC_API_URL,
  fetchOptions: { cache: "no-store" },
});

const authLink = setContext(async (_, { headers }) => {
  const session = await getServerSession(authOptions);
  const modifiedHeader = {
    headers: {
      ...headers,
      authorization: session?.jwt
        ? `Bearer ${session.jwt}`
        : `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
    },
  };
  return modifiedHeader;
});

export const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: from([authLink, httpLink]),
  });
});
phryneas commented 10 months ago

You might have encountered a regression that we had in 0.3.0 - could you please try it with 0.3.1 that I just released?

jnovak-SM2Dev commented 10 months ago

@phryneas Sorry, had to switch away from the app folder since there are a lot of issues with plugins. I have a test app i'll test this with later day.

jnovak-SM2Dev commented 10 months ago

@phryneas From what I can tell both getSession() and getServerSession(authOptions) still call the session endpoint on each call.

Tanish2002 commented 7 months ago

@phryneas An example with next-auth would be useful. My config is identical to @SM2DevLLC. I'm not sure if this is even related to next-auth but I've been trying to use useSuspenseQuery but I've been getting a weird error:

 ⨯ node_modules/.pnpm/@apollo+client@3.8.4_graphql@16.8.1_react-dom@18.2.0_react@18.2.0/node_modules/@apollo/client/errors/errors.cjs (33:0) @ new ApolloError
 ⨯ ApolloError: Cannot read properties of null (reading 'user')
    at new Promise (<anonymous>)
    at Array.forEach (<anonymous>)

I'm not able to trace back why it was created. It works fine if I simply do a useQuery.

phryneas commented 7 months ago

@Tanish2002 two different things ^^

phryneas commented 2 months ago

I'm doing some housekeeping so I'm closing some older issues that haven't seen activity in a while. If this is still relevant, please feel free to reopen the issue.

github-actions[bot] commented 2 months 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.