공부한거 정리하는 노트에요

병렬 라우팅 사용 시 Next.js 미들웨어가 리디렉션을 하지 않는 이슈

by 구설구설

미들웨어를 사용해 쿠키에 저장된 토큰 유무를 확인하고, 그 여부에 따라 리디렉션하도록 로그인 인증 처리를 구현했다.

import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("ACCESS_TOKEN");

  console.log(!!token, request.nextUrl.pathname, request.url)
  if (request.nextUrl.pathname.startsWith("/api/backoffice")) {
    if (request.nextUrl.pathname.endsWith("/login")) {
      console.log("1", !!token, request.nextUrl.pathname, request.url)
      return NextResponse.next();
    }

    if (!token) {
      console.log("2", !!token, new URL("/login", request.nextUrl.origin).toString())
      return NextResponse.redirect(new URL("/login", request.nextUrl.origin));
    }
  }


  if (token && request.nextUrl.pathname.startsWith("/login")) {
    console.log("3", !!token, request.nextUrl.pathname, request.url)
    return NextResponse.redirect(new URL("/", request.nextUrl.origin));
  }

  if (!token && !request.nextUrl.pathname.startsWith("/login")) {
    console.log("4", !!token, request.nextUrl.pathname, request.url)
    return NextResponse.redirect(new URL("/login", request.nextUrl.origin));
  }
  console.log("5", !!token, request.nextUrl.pathname)

  return NextResponse.next();
}

export const config = {
  matcher: [
    "/",
    // "/:path((?!api))",
    "/api/backoffice/:path*"
  ],
};

라우팅이 발생하거나 API를 호출할 때 요청에 포함된 쿠키를 확인해,

ACCESS_TOKEN이 있으면 ~NextResponse.next()~로 계속 진행하도록 설정했다.

토큰이 없으면 /login으로 리디렉션이 일어나며,

토큰이 있을 경우에는 /login으로 접근할 수 없도록 제한했다.

 

발생한 문제

토큰이 없어서 /login으로 리디렉션이 발생했으나,

리디렉션된 페이지로 브라우저가 전환되지 않고 API 요청의 응답처럼 동작하며 HTML 요소만 받아오는 문제가 있었다.

리디렉션이 발생해서 로그인 페이지를 정상적으로 가져온 모습이다.

그러나 이 상태에서 그대로 있을 뿐, 브라우저 주소가 변경되지 않고 화면 전환도 이루어지지 않았다.

 

열심히 찾아보았지만, 동일한 이슈를 경험한 사례는 찾기 어려웠다.

Next.js 14 미들웨어의 리디렉션 문제를 다룬 아래 GitHub 이슈에서
유사한 문제를 발견했으나, 제안된 해결책은 효과가 없었다.

 

NextJs 14 middleware redirect issue: User Keeps getting redirected to /login after successful Sign-In · Issue #59218 · vercel/

Link to the code that reproduces this issue https://github.com/anni1236012/nextjsMiddleware To Reproduce yarn dev All pages are protected via middleware except home page. Click on any link other th...

github.com

개인적으로는 미들웨어에서 리디렉션이 서버사이드에서 실행되기 때문에
브라우저에 영향을 주지 않거나, 실제 API 요청처럼 동작한다고 판단했다.

 

해결 시도

클라이언트 단계에서 리디렉션을 처리하려는 시도

리디렉션을 클라이언트 단계에서 처리하기 위해 useRouter 훅을 사용하려고 했다.
하지만 이 방식을 적용하려면 클라이언트 코드에서 쿠키에 접근할 수 있어야 했다.
그러나 Next.js 기본 설정상 클라이언트 코드가 쿠키에 접근할 수 없었다.

 

클라이언트 코드가 쿠키에 접근 가능하도록 구현 라이브러리를 찾기는 했지만,
추가 의존성 없이 해결할 방법을 찾기 위해서 이 방은 보류하였다.

 

문제의 원인

프로젝트를 진행하면서 Next.js의 병렬 라우팅을 사용하여 탭 구조를 개발하였다.

병렬 라우팅은 페이지 전환 이벤트를 발생시키지 않으므로 미들웨어도 작동하지 않는다.

내가 라우팅 중에 발생한 리디렉션이라고 생각했던 것은 사실 탭이 전환될 때 초기 데이터를 가져오는 api 요청의 리디렉션이었다.

그러니 당연하게도 리디렉션이 API 응답처럼 동작하게 되었다.

 

해결 방법

API 요청 시 토큰이 없으면 리디렉션 대신 JSON 오류 응답을 반환하도록 변경했다.

import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("ACCESS_TOKEN");

  console.log(!!token, request.nextUrl.pathname, request.url)
  if (request.nextUrl.pathname.startsWith("/api/backoffice")) {
    if (request.nextUrl.pathname.endsWith("/login")) {
      console.log("1", !!token, request.nextUrl.pathname, request.url)
      return NextResponse.next();
    }

    if (!token) {
      console.log("2", !!token, new URL("/login", request.nextUrl.origin).toString())
      return NextResponse.json({ error: "Unauthorized" }, { status: 302 });
    }
  }

  if (request.nextUrl.pathname.startsWith("/api/interface")) {
    if (!token) {
      console.log("2", !!token, new URL("/login", request.nextUrl.origin).toString())
      return NextResponse.json({ error: "Unauthorized" }, { status: 302 });
    }
  }


  if (token && request.nextUrl.pathname.startsWith("/login")) {
    console.log("3", !!token, request.nextUrl.pathname, request.url)
    return NextResponse.redirect(new URL("/", request.nextUrl.origin));
  }

  if (!token && !request.nextUrl.pathname.startsWith("/login")) {
    console.log("4", !!token, request.nextUrl.pathname, request.url)
    return NextResponse.redirect(new URL("/login", request.nextUrl.origin));
  }
  console.log("5", !!token, request.nextUrl.pathname)

  return NextResponse.next();
}

export const config = {
  matcher: [
    "/",
    "/login",
    // "/:path((?!api))",
    "/api/backoffice/:path*",
    "/api/interface/:path*",
  ],
};

클라이언트에서 응답 처리

JSON 오류 응답을 클라이언트에서 처리하기 위해 ~axios.interceptor~를 사용했다.

특정 응답 코드(302)를 확인해 /login으로 라우팅하도록 설정했다.

import axios from "axios";
import { useRouter } from "next/navigation";

export const axiosInstance = axios.create();

export const useAxiosInterceptor = (router: ReturnType<typeof useRouter>) => {
  axiosInstance.interceptors.response.use(
    (response) => response, // 응답이 정상이라면 넘어감
    (error) => {
      if (error.response?.status === 302) { // 에러의 응답 코드가 302라면 /login으로 라우팅
        router.push("/login"); 
      }
      return Promise.reject(error);
    }
  );
};

API 요청 시 axiosInstance를 사용해 응답을 확인했다.

 

import { axiosInstance } from "@/shared";

export const getMenuInfo = async () => {
  try {
    const response = await axiosInstance.get("/api/backoffice/menuInfo", { 
    // axios가 아닌 axiosInstance 사용
      headers: {},
    });

    return response.data;
  } catch (error) {
    return error;
  }
};

그리고 클라이언트의 layout.tsx에서 useAxiosInterceptor를 사용해
병렬 라우팅 구조 내 모든 API 요청을 확인할 수 있도록 했다.

import { cn, useMenuStore, useAxiosInterceptor } from "@/shared";
import { useRouter } from "next/navigation";
  
export default function ContentLayout(props : pageProps) {  
  const router = useRouter();

  useAxiosInterceptor(router);
  
  return (
  	// 레이아웃 구성 요소
   );
 }

 

정리

  1. Next.js 미들웨어는 페이지 전환이나 API 호출을 전처리할 수 있다.
  2. 미들웨어의 Redirect는 페이지 전환에서는 브라우저 주소를 변경하지만, API 호출에서는 Rewrite와 동일한 동작을 한다.
  3. 병렬 라우팅은 페이지 전환 이벤트를 발생시키지 않으므로 미들웨어가 작동하지 않는다.
  4. Redirect 대신 JSON 오류 응답을 반환하고, 클라이언트에서 이벤트를 관찰해 처리하면 문제를 해결할 수 있다.

'일기' 카테고리의 다른 글

Next.js Socket Hangup 오류  (0) 2025.01.03
GeoServer Layer 테두리 색상 제거  (0) 2024.08.30

블로그의 정보

공부중임

구설구설

활동하기