image

문제

인증 페이지에 비인증 회원이 접근하는 경우 로그인 페이지로 redirect 되록 해야 했는데 이전 방식을 사용할 수 없어서 고민이었습니다.

기존 방식

CRA에선 라는 컴포넌트를 만들고 token여부에 따라 해당 페이지 또는 로그인 페이지로 리다이렉트 하도록 처리했습니다.

  • App.jsx
<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
    <RouteIf token={token} path="/todoList" element={<TodoList />} />
  </Routes>
</BrowserRouter>
  • RouteIf.jsx
const RouteIf = ({token, path, element}) => {
  return ({ token
  ? <Route path={path} element={element}/>
  : <Navigate to='/login' />
}

현재 상황

Next.js에선 라우팅을 알아서 처리하기 어디서 해당 조건문을 줘야 할 지 막막했습니다.


고안한 방법들

~~api routes 사용

pages 로 접근 전 api를 가로채면 되지 않을까?

  • 단점: api 파일을 일일히 만들고 페이지 이동할 때도 ‘/api를 붙여야 함…

  • 문제: 현재 ‘/api’를 붙이는 요청은 ‘백엔드서버주소/요청url’로 변환하는 rewrite 기능을 쓰고 있기에 이런 식으로 페이지 이동을 처리하면 오류가 날 수 밖에 없음

페이지의 서버 사이드에서 처리하면?

페이지 이동시 getServerSideProps에서 context.req.cookies로 토큰에 접근 가능 여기서 redirect 하면되지 않을까

  • 문제 : getServerSide라고 해도 pre-rendering을 하기에 해당 페이지를 보고 이동해야함

중간 페이지 사용

  • page> authRoute 라는 페이지를 만들고 해당 페이지에 먼저 보낸 뒤 여기에 authRoute/페이지url 로 받은 path variable을 토큰 유무에 따라 router.push에 넣어주거나, 로그인 페이지로 보내는 방식

  • 단점 : 페이지 계층이 복잡, 페이지를 한 번 들어간 다음 routing을 하면 화면 깜빡임이 존재

그럼 고차 컴포넌트는?

page> authoRoute에서 아예 return 을 해주면 되지 않나?

  • 인증이 필요한 페이지를 컴포넌트로 만들고 authRoute 페이지에서 import한 다음 pageUrl에 따라 다른 컴포넌트를 반환해주는 컴포넌트를 return해주면 깜빡임 없이 routing 가능

  • 단점: Nextjs 페이지 폴더 라우팅 방식을 무시, 페이지 위계질서 무시, 아토믹 디자인 시스템 무시, 전체적으로 복잡한 코드

  • 문제: pathVariable이 필요한 페이지들은 어쩔건데? 전부 커스터 마이징할래?

훅 사용

고차컴포넌트는 클래스형 컴포넌트에서 사용하던 방식이라, 훅이 좀 더 적합하다고 생각했고 다음과 같이 구현하면 어떨까 고민했습니다.

const useAuthRouter = (pathUrl) => {
  const token = getToken();
  const router = useRouter();

  if (!token) {
    router.push("/login");
  } else {
    router.push(`/${pathUrl}`);
  }
};
export default useAuthRouter;
  • 문제
    하지만, router 하는 부분은 거를 수 있지만 직접 url을 입력하거나 Link 태그를 사용하면 거를 수 없다는 치명적인 문제가 있어 제외했습니다.


해결

Nextjs 미들웨어 사용

팀원과 이문제를 상의하다 발견해낸 방법으로 Nextjs에서 페이지 url 변화를 감지해 중간 처리를 하면 됩니다. 아래처럼 NextRequest를 에서 pathname, cookie를 받아 조건문에 따라 인증이 필요한데 토큰이 없으면 로그인 페이지로 리다이렉트하게 만들 수 있습니다.

또 토큰이 있는데 로그인 회원가입 페이지로 들어가는 걸 막을 수도 있습니다. 이 방법은 서버에서 처리하는 방식이라 화면 깜빡임도 없고, 인증처리를 위한 api를 따로 만들 필요가 없어서 훨씬 효율적입니다.

  • 작동 방식

export const config = { matcher: [ req url목록]}

config의 match에 등록한 요청인 경우만 middleware가 동작하고 토큰 유무에 따라 로그인 페이지로 redirect해줍니다.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { TOKEN_KEY } from "@constants/token";
import { SIGNUP_URL, LOGIN_URL, HOME_URL } from "@constants/pageUrl";

export function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;
  const url = req.nextUrl.clone();
  if (pathname === LOGIN_URL && req.cookies.get(TOKEN_KEY)) {
    url.pathname = "/";
    return NextResponse.redirect(url);
  } else if (pathname === SIGNUP_URL && req.cookies.get(TOKEN_KEY)) {
    url.pathname = HOME_URL;
    return NextResponse.redirect(url);
  } else if (pathname !== SIGNUP_URL && !req.cookies.get(TOKEN_KEY)) {
    url.pathname = LOGIN_URL;
    return NextResponse.redirect(url);
  }
}

export const config = {
  matcher: [
    "/create-menu",
    "/edit-menu/:path*",
    "/myInfo/:path*",
    "/user/:path*",
    "/signup",
    "/login",
  ],
};

댓글남기기