[Next.js] 미들웨어(Middleware)

2024. 5. 21. 16:11
반응형

미들웨어(Middleware)란?

  • 페이지를 렌더링하기 전, 서버측에서 실행되는 함수(Next.js 12버전부터 안정화)
  • 캐싱된 페이지보다 먼저 수행되며, request와 response 객체에 접근할 수 있음
  • 이와 같은 원리를 활용하여 부가적인 처리를 할 수 있음
  • 간단히 말하면 ‘특정 요청 전에 무언가를 수행할 수 있게 해주는 기능’
  • 공식문서(링크)

 

사용 예시

  • 화면 렌더링 전 사용자 권한 확인
  • i18n 라우팅(사용자가 사전에 설정한 언어로 화면을 그려줄 때)
  • 특정 상황에서 response, request header의 수정이 필요할 때
  • A/B 테스트가 필요할 때

 

 

사용법

export async function middleware() {
  ...
}

// matcher에 포함된 경로에서만 미들웨어 실행
export const config = {
  matcher: '/admin/:path*',
};

 

 

 

  • 프로젝트 root 폴더에 middleware.ts 파일을 만들고 export 해주면 적용됨
  • matcher를 같이 export 해주면, 그 안에 들어있는 경로에서만 미들웨어가 적용됨 (아래는 admin 페이지에서만 미들웨어가 작동되길 바라는 케이스)
    • 배열로 넣어 여러 페이지에 적용할 수 있다
    • 정규식을 사용할 수 있다(특정 경로만 제외하는 경우 등에서 활용)
    • 만약 matcher 내용을 /a/:path 로 설정했다면, /a/b에서는 미들웨어가 실행되지만, /a/b/c 에서는 실행되지 않음
    • /a/b/c 를 포함시키고 싶다면, /a/:path* 라고 작성해주면 됨

 

 

아래와 같이 request, response를 꺼내어 쓸 수 있다.

 

import { NextRequest, NextResponse } from 'next/server';

export async function middleware(req: NextRequest) {
  const response = NextResponse.next();
  
  return response;
}

 

 

 

NextResponse는 아래와 같은 역할을 할 수 있다.

  • 요청이 들어온 경로에서 다른 경로로 redirect 시켜주기
    • return NextResponse.redirect(new URL('/', request.nextUrl.origin))
  • 요청이 들어오는 경로는 유지하고 rewrite 시켜주기
    • return NextResponse.rewrite(new URL('/', request.nextUrl.origin))
  • response 쿠키/헤더 설정
    • response.cookies.set('key', 'value')
    • response.cookies.get('key')?.value
  • 새로운 응답 return
if(조건) {
  return new NextResponse(
    JSON.stringify({ success: false, message: 'failed...' }),
    { status: 401, headers: { 'content-type': 'application/json' } }
  )
}

 

 

 

 

응용하기

 

case 1. 비회원이 auth 필요 페이지로 접근시도하거나, 이미 로그인한 회원이 로그인컴포넌트가 있는 페이지로 접근했을 때

export async function middleware(req: NextRequest) {
  const response = NextResponse.next();
  const { pathname } = req.nextUrl;
  const token = req.cookies.get(AUTH_TOKEN_KEY)?.value;
  
  // 비로그인 상태에서 다른탭 접속시도할 때
  if (!token && pathname !== '/login') {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  // 로그인세션 있는 상태에서 로그인페이지 접속하면 대시보드로 보냄
  if (token && pathname === '/login') {
    return NextResponse.redirect(new URL('/dashboard', req.url));
  }
  
  return response;
}

 

 

case 2. 로그인한 회원별 접근권한 페이지 확인하고 막아주기

(ex. 특정 회원이 page A에는 접근할 수 있지만, page B에는 접근권한이 없을 때)

 

// 권한이 필요없는 페이지 목록
const notUseAuthorityPageList = ['pageC'];

export async function middleware(req: NextRequest) {

    // 로그인한 유저의 jwt 토큰에 담긴 권한 decode 후 쿠키저장
    const decoded: DecodedTokenType | undefined = token
      ? jwtDecode(token)
      : undefined;
    
    // 이 회원이 pageA, B에 권한 가지고있는지 여부 (canPageA...는 DB에서 받아온 값)  
    const permissions: Record<UrlType, boolean> = {
    pageA: decoded?.canPageA ?? false,
    pageB: decoded?.canPageB ?? false,
    
    // 쿠키 저장
    response.cookies.set(PERMISSIONS, JSON.stringify(permissions));
    
    // 접근권한 필요한 페이지인지 아닌지 판별
    const [_, admin, sideMenu, sideMenuSubPage] = pathname.split('/');
    const isNotUseAuthorityPage = notUseAuthorityPageList.some((x) => {
      if (!sideMenu) return true;
      return sideMenu.startsWith(x);
    });

    // 접근권한 필요없는페이지
    if (isNotUseAuthorityPage) {
      return response;
    }
  
    // 접근권한 필요한 페이지
    else {
      if (!decoded) return NextResponse.redirect(new URL('/', req.url));
      else {
        // 권한없는데 접근시도할때 초기페이지로 이동
        if (!permissions[sideMenu as UrlType]) {
          return NextResponse.redirect(new URL('/', req.url));
        }
      }
    }
  };
  
  return response;
}

 

 

반응형

BELATED ARTICLES

more