[디버깅] NextAuth 앱 Vercel 배포 시 오류 해결 방법(Type error: Route "src/app/api/auth/[...nextauth]/route.ts" does not match the required types of a Next.js Route.)

YouGyoung·2024년 3월 28일
0
post-thumbnail

들어가기 전

리팩토링 중에 폴더 구조, 모듈 이름 등 다양한 변경이 발생했습니다.

이런 변경 후에는 해당 파일이 참조된 위치나 이름을 수정해 줘야하는데, 미루고 미뤘죠..

그 결과, 식은땀을 흘리며 코드 수정을 진행해야 했습니다.

다행스럽게도 잘못된 참조를 수정하고 빠르게 수정이 불가능한 부분은 주석처리하여 정상적으로 배포가 되었습니다.


사진 상에서는 몇 개 안 보이지만 아래에 10개가 넘는 Error난 배포들이 있습니다.

NextAuth 배포 오류

배포 오류 중 골치아팠던 오류는 NextAuth 관련 오류였습니다.

다른 오류들은 단순 참조 오류였기 때문에 임시적인 주석처리와 수정으로 해결할 수 있었습니다만, NextAuth 오류는 뭐가 문제인지 도통 모르겠더군요.

오류의 내용은 다음과 같았습니다.

오류 내용

src/app/api/auth/[...nextauth]/route.ts
Type error: Route "src/app/api/auth/[...nextauth]/route.ts" does not match the required types of a Next.js Route.
  "handler" is not a valid Route export field.

오류 코드

import NextAuth, { AuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { FirebaseError } from 'firebase/app';
import { FirebaseAuthError } from '@/error/firebaseAuthError';
import signInFirebase from '@/_utils/signInFireBase';

const handler: AuthOptions = NextAuth({
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  providers: [
    CredentialsProvider({
      name: 'showfinnmore',
      credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials || !credentials.email || !credentials.password)
          return null;
        try {
          const user = await signInFirebase(
            credentials.email,
            credentials.password
          );
          if (user) {
            return {
              id: user.id,
              name: user.name,
              email: user.email,
            };
          }
          return null;
        } catch (error) {
          if (error instanceof Error) {
            const firebaseError = error as FirebaseError;
            const errorCode = firebaseError.code;
            const message = firebaseError.message;

            throw new FirebaseAuthError(errorCode, message);
          } else {
            throw new Error('알 수 없는 에러가 발생했습니다.');
          }
        }
      },
    }),
  ],
  pages: {
    signIn: '/account/login',
  },
  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl;
    },
  },
});

export { handler as GET, handler as POST };

삽질

폴더 이름이 문제일까, 파일 이름이 문제일까, 여러 번의 삽질 후 빛과 같은 글을 찾아냈습니다.
Why does my NextAuthJS discord login work in the test environment but cannot be built to production? (NextJS 13)

여기에서 가장 반응이 많은 댓글을 보면 다음의 코드와 같이 authOptionsNextAuth로 넘긴 후 반환 받은 값을 handler로 할당해서 export하면 해결이 된다고 합니다.

import NextAuth, { NextAuthOptions } from "next-auth";
import DiscordProvider from "next-auth/providers/discord";

const authOptions:NextAuthOptions = {
  providers: [
    DiscordProvider({
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
    })
  ]
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

제 코드를 다음과 같이 수정했습니다.

import NextAuth, { AuthOptions, NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { FirebaseError } from 'firebase/app';
import { FirebaseAuthError } from '@/error/firebaseAuthError';
import signInFirebase from '@/utils/signInFirebase';

const authOptions: NextAuthOptions = NextAuth({
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  providers: [
    CredentialsProvider({
      name: 'showfinnmore',
      credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials || !credentials.email || !credentials.password)
          return null;
        try {
          const user = await signInFirebase(
            credentials.email,
            credentials.password
          );
          if (user) {
            return {
              id: user.id,
              name: user.name,
              email: user.email,
            };
          }
          return null;
        } catch (error) {
          if (error instanceof Error) {
            const firebaseError = error as FirebaseError;
            const errorCode = firebaseError.code;
            const message = firebaseError.message;

            throw new FirebaseAuthError(errorCode, message);
          } else {
            throw new Error('알 수 없는 에러가 발생했습니다.');
          }
        }
      },
    }),
  ],
  pages: {
    signIn: '/account/login',
  },
  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl;
    },
  },
});

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Vercel에서 배포 시 오류가 표시되지는 않았지만 콘솔에서 TypeError: options.providers is not iterable 오류가 발생했습니다.

생각해보면 NextAuth로 받은 데이터(authOptions)를 다시 한 번 NextAuth로 보내 반환받는 게 이상했습니다. 다시 위의 코드를 보니 제가 잘못 작성한 거 였네요.

다음과 같이 handler를 삭제하고 authOptions을 내보내니 TypeError: options.providers is not iterable 오류는 해결되었는데요..

import NextAuth, { AuthOptions, NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { FirebaseError } from 'firebase/app';
import { FirebaseAuthError } from '@/error/firebaseAuthError';
import signInFirebase from '@/utils/signInFirebase';

const authOptions: NextAuthOptions = NextAuth({
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  providers: [
    CredentialsProvider({
      name: 'showfinnmore',
      credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials || !credentials.email || !credentials.password)
          return null;
        try {
          const user = await signInFirebase(
            credentials.email,
            credentials.password
          );
          if (user) {
            return {
              id: user.id,
              name: user.name,
              email: user.email,
            };
          }
          return null;
        } catch (error) {
          if (error instanceof Error) {
            const firebaseError = error as FirebaseError;
            const errorCode = firebaseError.code;
            const message = firebaseError.message;

            throw new FirebaseAuthError(errorCode, message);
          } else {
            throw new Error('알 수 없는 에러가 발생했습니다.');
          }
        }
      },
    }),
  ],
  pages: {
    signIn: '/account/login',
  },
  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl;
    },
  },
});

export { authOptions as GET, handler as POST };

다시 Type error: Route "src/app/api/auth/[...nextauth]/route.ts" does not match the required types of a Next.js Route. Invalid configuration "GET": 오류가 발생했습니다.

해결 방법

Auth 관련 옵션을 authOptions을 객체에 담아 NextAuth로 넘겨 받은 데이터를 handler에 저장하니 해결되었습니다.

어째서 객체 리터럴은 안 되고 변수를 통해 전달하는 것은 되는지는 정보가 없네요 ...

어쨋든 아래 글에서 나온 코드대로만 하면 잘 됩니다 ! 괜히 이것도 되겠지 하다가,,,,ㅠ
(Why does my NextAuthJS discord login work in the test environment but cannot be built to production? (NextJS 13))

import NextAuth, { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { FirebaseError } from 'firebase/app';
import { FirebaseAuthError } from '@/error/firebaseAuthError';
import signInFirebase from '@/_utils/signInFirebase';

const authOptions: NextAuthOptions = {
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  providers: [
    CredentialsProvider({
      name: 'showfinnmore',
      credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials || !credentials.email || !credentials.password)
          return null;
        try {
          const user = await signInFirebase(
            credentials.email,
            credentials.password
          );
          if (user) {
            return {
              id: user.id,
              name: user.name,
              email: user.email,
            };
          }
          return null;
        } catch (error) {
          if (error instanceof Error) {
            const firebaseError = error as FirebaseError;
            const errorCode = firebaseError.code;
            const message = firebaseError.message;

            throw new FirebaseAuthError(errorCode, message);
          } else {
            throw new Error('알 수 없는 에러가 발생했습니다.');
          }
        }
      },
    }),
  ],
  pages: {
    signIn: '/account/login',
  },
  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl;
    },
  },
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

마치며

Vercel에서 배포 오류가 나타나도 리팩토링 끝나고 해야지 하면서 미뤘습니다만, 앞으로 바로바로 해결해야겠습니다.

이름이나 위치 변경하면 바로바로 import된 곳에서 반영을 해 주는 것도 중요한 것 같습니다.

리팩토링 시작할 때 branch를 나눠서 해당 branch에 push를 했어야 했는데, 그 부분을 놓쳐 일이 조금 복잡해 진 거 같습니다..
임시적으로 주석처리한 부분을 다시 살려낸 후에 branch를 나눌 예정입니다.

profile
프론트엔드 개발자

0개의 댓글

관련 채용 정보