Fittering 개발 기록 - Next.js 13 redirect 관련 트러블 슈팅

thumbzzero·2023년 8월 9일

관련 공식 문서 : https://nextjs.org/docs/app/api-reference/functions/redirect

구현 기능


  • 로그인 안 된 사용자가 서비스 내부 페이지 접속 시 로그인 페이지로 redirect 되도록
  • 로그인된 상태에서 로그인/회원가입 페이지 접근 시 메인 페이지로 redirecte 되도록

로그인 여부 구분 방법

  1. 로그인 시 발급받는 JWT 토큰을 로컬 스토리지에 저장함 -> 로컬 스토리지에 있는 값이 null이면 로그인이 안 된 상태
  2. 로그인 후 1시간이 지나면 토큰 유효 시간이 만료됨 -> 로컬 스토리지에 있는 토큰이 만료된 경우 백엔드 API 요청 시 응답으로 403 코드가 날라옴

구현 과정 & 트러블 슈팅


Error: NEXT_REDIRECT at getRedirectError

customFetch 함수 : 로그인할 때 로컬 스토리지에 저장해 놓은 토큰 값을 기본 헤더로 포함하고, API 요청 BASE_URL을 추가해서 fetch 요청 후 Response를 반환 -> Response의 status가 401이면 로그인 페이지로 redirect 함

[초기 구현 코드]

export async function customFetch(
  requestURL: string,
  options?: RequestInit
): Promise<Response> {
  const headers = { ...defaultHeaders, ...options?.headers };
  const mergedURL = BASE_URL + requestURL;
  const mergedOptions = { ...options, headers };

  const response = await fetch(mergedURL, mergedOptions);

  if (response.status === 401) {
    redirect('/login');
  }

  return response;
}

-> Error: NEXT_REDIRECT at getRedirectError

next.js 깃허브 리포지토리에 남겨진 이슈와 스택 오버플로우를 참고해서 수정해보았지만 거의 해결이 되지 않음

[수정 코드]

export async function customFetch(
  requestURL: string,
  options?: RequestInit
): Promise<Response> {
  const headers = { ...defaultHeaders, ...options?.headers };
  const mergedURL = BASE_URL + requestURL;
  const mergedOptions = { ...options, headers };

  const response = await fetch(mergedURL, mergedOptions);

  if (response.status === 401) {
    window.location.replace('/login');
  }

  return response;
}
/* redirect.d.ts */
export declare function getRedirectError(url: string, type: RedirectType): RedirectError<typeof url>;
/**
 * When used in a React server component, this will insert a meta tag to
 * redirect the user to the target page. When used in a custom app route, it
 * will serve a 302 to the caller.
 *
 * @param url the url to redirect to
 */
export declare function redirect(url: string, type?: RedirectType): never;
/**
 * Checks an error to determine if it's an error generated by the
 * `redirect(url)` helper.
 *
 * @param error the error that may reference a redirect error
 * @returns true if the error is a redirect error
 */
export declare function isRedirectError<U extends string>(error: any): error is RedirectError<U>;
/**
 * Returns the encoded URL from the error if it's a RedirectError, null
 * otherwise. Note that this does not validate the URL returned.
 *
 * @param error the error that may be a redirect error
 * @return the url if the error was a redirect error
 */

Next.js 공식 문서와 redirect() 정의를 보면 redirect()NEXT_REDIRECT 에러를 발생시키는 것은 맞으나 사용 예제를 보면 올바르게 사용한 것 같은데 문제가 되는 이유를 고민해보았다.

그러다 layout.tsx 파일에 로컬 스토리지에서 토큰 값을 읽어왔을 때 null이면 로그인 페이지로 redirect 되도록 redirect('/login');을 코드를 작성했는데 정상적으로 로그인 페이지로 redirect 되는 것을 확인하게 되었다.

확실하지는 않지만 Next.js 공식 문서에 서버 컴포넌트, 클라이언트 컴포넌트, 라우트 핸들러, Server Actions에 사용될 수 있다고 적혀있는데 내가 작성한 customFetch 함수는 여기에 속하지 않아서 그렇지 않을까라고 추측해 보았다.

그러나 이 이유가 확실한지는 확인이 필요할 것 같다.

크롬 - 리다이렉션한 횟수가 너무 많습니다. 에러

위에 작성한 layout.tsx 파일의 로컬 스토리지에서 토큰 값을 읽어왔을 때 null이면 로그인 페이지로 redirect 되도록 하는 redirect('/login');코드

로그인 없이 메인 페이지로 바로 접속하자 로그인 페이지로 redirect가 되었으나 리다이렉션한 횟수가 너무 많습니다 라는 에러가 발생하였다.

-> 해결하기 위해 쿠키 삭제도 해보았으나 해결되지 않음

원인
: 작성한 layout.tsx이 app 디렉토리 안에 바로 있는 가장 상위의 layout 파일이어서 로그인 페이지에서도 적용되는 파일임 -> 로그인 페이지로 redirect 된 이후에도 세션 스토리지에 토큰이 없어 로그인 페이지로 무한대로 redirect가 됨

해결
: Route Group(https://nextjs.org/docs/app/building-your-application/routing/route-groups)을 사용해서 (auth) 디렉토리와 (service) 디렉토리를 구분한 후 layout.tsx 파일을 별도로 둠

1개의 댓글

comment-user-thumbnail
2025년 2월 23일

엄청나게 늦은 감이 없지 않아 있지만... 원인을 알려드리자면 Next.js에서는 error를 통해 특정 동작을 처리합니다. 그중 하나가 redirect로 error의 digest를 분석하면 url과 https 코드, 동작 유형등이 적혀 있어요. 그래서 redirect 함수는 단지 에러를 던지는 것 뿐인데 이걸 커스텀 함수에서 try catch 구문으로 에러를 핸들링하려하면 해당 구문에서 에러를 감지해서 문제를 일으킵니다.

답글 달기