Next-Auth의 server session

Jeonghun·2024년 7월 5일
0

NextJS

목록 보기
4/6
post-thumbnail
post-custom-banner

이번 포스팅에서는 Next-Auth 의 세션을 사용할 때 마주할 수 있는 이슈에 대해 간단한 경험을 공유하고자 합니다.


문제 사항

개발을 진행하던 중 위와 같이 '깜빡임 (ui 불일치)' 현상이 발생했습니다.

아래는 문제가 발생한 부분의 코드입니다.

"use client";

import { useSession } from "next-auth/react";
// import ...

export const MainHeader = () => {

  const { data: session, status } = useSession();

  return (
    <Header
      leftSlot={
        <Link href="/">
          <Image src={IMAGES.LOGO} alt="로고" width={100} height={30} />
        </Link>
      }
      rightSlot={
        <Flex align="center" justify="center" className="sm-max:gap-0 gap-3">
          // ...
          {status === "authenticated" ? (
            <Link href={`/user/info/${session?.user?.id}`}>
              <Avatar className="sm-max:hidden border border-border border-solid">
                <AvatarImage src={session?.user?.image!} />
                <AvatarFallback>{session?.user?.name}</AvatarFallback>
              </Avatar>
            </Link>
          ) : (
            <div className="sm-max:hidden">
              <LoginModal />
            </div>
          )}
        </Flex>
      }
    >
      {null}
    </Header>
  );
};

여러분들은 문제의 원인을 찾으셨나요? 해결 방법을 알아보기 전에 CSRSSR 에 대해서 간단히 살펴보고 넘어가고자 합니다.

CSR (Client Side Rendering)

CSR (Client Side Rendering) 은 말 그대로 클라이언트 (브라우저) 단에서 렌더링을 처리하는 방식으로 동작 방식은 다음과 같습니다.

1. 사용자가 웹 사이트를 방문하면, 브라우저가 서버에 콘텐츠를 요청합니다.
2. 서버는 빈 뼈대만 있는 HTML을 응답합니다.
3. 브라우저가 연결된 JavaScript 링크를 통해 서버로부터 다시 JS 파일을 다운로드 합니다.
4. 받아온 JS를 통해 페이지를 만들어 렌더링 합니다.

SSR (Server Side Rendering)

SSR (Server Side Rendering) 은 렌더링을 서버 단에서 처리하는 방식으로 아래와 같이 동작합니다.

1. 사용자가 웹 사이트를 방문하면, 브라우저가 서버에 콘텐츠를 요청합니다.
2. 서버는 페이지에 필요한 데이터를 즉시 로드해 HTML을 생성하고, CSS를 적용하여 완성된 HTML과 JS를 브라우저에 반환합니다.
3. 브라우저는 이를 받아 렌더링 합니다. (JS를 읽기 전 까진 상호작용이 불가능 합니다.)
4. 브라우저가 JS 파일을 읽습니다.
5. JS를 실행하여 페이지를 상호작용 가능하게 만듭니다.

위 과정에서 3 - 5번 즉, 렌더링 된 HTML DOM 요소에 JS 파일을 덮어씌우는 과정을 hydration 이라고 합니다.

문제 원인

위의 문제 코드를 보면 useSession 훅을 통해 세션 정보에 접근하고 있습니다. 이 때 해당 컴포넌트는 use client 지시어를 사용했기 때문에 서버 측에서 렌더링 된 후, 다시 클라이언트 측에서 렌더링 됩니다.

이 과정에서 문제가 발생하는데요, 서버 측에서 렌더링 할 땐 useSession 훅의 세션 정보에 접근할 수 없기 때문에 로그인 버튼을 보여주었다가, 클라이언트 측에서 렌더링 할 때 세션 정보를 주입하여 ui가 달라지면서 깜빡이는 것 처럼 보이게 됩니다.

해결 방법

해결 방법은 간단합니다. 클라이언트 컴포넌트에서 발생하는 문제이기 때문에 이를 서버 컴포넌트로 바꿔주면 되는데요,

Next-Auth 에서는 서버 단에서 세션 정보에 접근할 수 있도록 getServerSession 함수를 제공하고 있습니다.

사용방법 또한 간단합니다. 우선 authOptions 를 작성해줍니다. 이는 Next-Auth를 사용한다면 이미 아래와 비슷하게 작성되어 있을겁니다.

// import ...

export const authOptions = {
  secret: process.env.NEXTAUTH_SECRET,
  providers: [
    KakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID as string,
      clientSecret: process.env.KAKAO_CLIENT_SECRET as string,
    }),
  ],
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: "jwt" as const,
  },
  callbacks: {
    session: ({ session, token }: { session: Session; token: JWT }) => ({
      ...session,
      user: {
        ...session.user,
        id: token.sub,
      },
    }),
  },
};

authOptions를 getServerSession 함수의 인자로 전달할 수 있습니다. 저는 세션 정보를 사용하는 곳이 많아 별도의 유틸 함수로 분리했습니다.

// app/shared/utils/getServerSession.ts

"use server";

import { getServerSession as getSession } from "next-auth/next";
import { authOptions } from "@/app/shared/lib/next-auth";
import { AuthOptions, Session } from "next-auth";

export const getServerSession = async (): Promise<Session | null> => {
  const session = await getSession(authOptions as AuthOptions);

  return session;
};

그 후 위의 클라이언트 컴포넌트에서 함수를 사용해 세션 정보를 받아올 수 있습니다.

// use client를 제거합니다.

import { getServerSession } from "@/app/shared/utils";
// import ...

export const MainHeader = async () => {
	
  const session = await getServerSession();
  
  // getServerSession 함수는 useSession에서 제공하는 `status` 값이 담겨 있지 않습니다.
  // 따라서 session이 정상적으로 반환 되었을 경우 'authenticated' 로 설정해줍니다.
  const status = session ? "authenticated" : "unauthenticated";

  return (
    <Header
      leftSlot={
        <Link href="/">
          <Image src={IMAGES.LOGO} alt="로고" width={100} height={30} />
        </Link>
      }
      rightSlot={
        <Flex align="center" justify="center" className="sm-max:gap-0 gap-3">
          // ...
          {status === "authenticated" ? (
            <Link href={`/user/info/${session?.user?.id}`}>
              <Avatar className="sm-max:hidden border border-border border-solid">
                <AvatarImage src={session?.user?.image!} />
                <AvatarFallback>{session?.user?.name}</AvatarFallback>
              </Avatar>
            </Link>
          ) : (
            <div className="sm-max:hidden">
              <LoginModal />
            </div>
          )}
        </Flex>
      }
    >
      {null}
    </Header>
  );
};

수정 후

로그인 버튼이 렌더링 되지 않고, 정상적으로 사용자 정보가 렌더링 되는 것을 확인할 수 있습니다.

profile
안녕하세요, 프론트엔드 개발자 임정훈입니다.
post-custom-banner

0개의 댓글