Middleware | Next.js

Bori·2023년 5월 28일
1

Next.js

목록 보기
7/12
post-thumbnail

Middleware?

미들웨어는 소프트웨어 시스템에서 요청과 응답 사이에 위치하여 중간 처리를 수행하는 소프트웨어 구성 요소입니다.
일반적으로 클라이언트로부터 요청이 서버에 도달하기 전에 중간에 처리되는 로직을 구현하는데 사용합니다.
예를 들어 인증, 인가, 오류 처리 등 미들웨어로 구현하여 프로그램의 기능을 확장하거나 요청을 처리하는 과정을 변경할 수 있습니다.

미들웨어는 양 쪽을 연결하여 데이터를 주고 받을 수 있도록 중간에서 매개 역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 컴퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해 주는 소프트웨어를 말한다. 3계층 클라이언트/서버 구조에서 미들웨어가 존재한다. 웹 브라우저에서 데이터베이스로부터 데이터를 저장하거나 읽어올 수 있게 중간에 미들웨어가 존재하게 된다.
위키백과 - 미들웨어

왜 미들웨어를 사용하게 되었냐면..

예전 개인 프로젝트에서는 로그인을 하면 쿠키에 토큰을 저장하여 저장된 토큰이 있을 경우, 페이지 접근이 가능하고 그렇지 않은 경우 로그인 페이지로 라우팅 처리하는 방식을 이용했습니다.

// js-cookie 라이브러리 사용
// 쿠키에 저장된 token을 가져옴
const token = Cookies.get('token');

useEffect(() => {
  // token이 없는 경우 로그인 페이지로 이동
  if (!token) {
    router.push('/login');
  }
}, []);

useEffect를 통해 토큰 여부를 확인한 후 페이지 이동이 일어나는데, 이 과정에서 해당 페이지가 잠깐 페이지가 노출됩니다.
이를 해결하고자 이번 프로젝트에서는 Next.js에서 제공하는 middleware를 사용하게 되었습니
다. 미들웨어를 설정하면 페이지 노출없이 바로 리다이렉트를 할 수 있습니다.

로그인 한 사용자가 로그인/회원가입 페이지에 접근하지 못하도록 제한하는 코드를 미들웨어에 적용해보았습니다.

Middleware with Next.js

미들웨어를 사용하기 위해 middleware.ts(또는 .js) 파일을 생성합니다.
이 파일은 pages 디렉토리와 같은 레벨에 있어야 합니다.

과거에는 _middleware.ts 파일을 디렉토리 내에서 생성하여 적용할 수 있었으나, Next.js가 업데이트 되면서 중첩 미들웨어를 지원하지 않고, 단일 루트 미들웨어를 지원하는 것으로 변경되었습니다.
또한, 파일명에 _ 프리픽스도 사용하지 않습니다.

_middleware.ts의 중첩 미들웨어를 적용하게 되면 다음과 같은 에러가 발생합니다.

⇒ 미들웨어 관련 내용을 찾아보면서 이 전 버전에서는 동작했던 방식이 현재는 동작하지 않아 블로그를 작성하게 되었습니다.

관련 링크

예시 코드

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
export function middleware(request: NextRequest) {
  // matcher와 일치하는 경로에 접근 시 '/home'으로 리다이렉트
  return NextResponse.redirect(new URL('/home', request.url));
}
 
// matcher에 포함된 특정 경로에서만 middleware 실행
export const config = {
  matcher: '/about/:path*',
};

Matcher

matcher를 사용하여 미들웨어를 실행할 특정 경로를 설정합니다. matcher에는 몇 가지 규칙이 있습니다.

  • 반드시 /로 시작해야 합니다.
  • /about/:path matches는 /about/a/about/b를 포함할 수 있지만 /about/a/c는 포함할 수 없습니다.
  • named parameter에 수식자를 사용할 수 있습니다.
    • : 기호로 시작해야합니다.
    • * 는 0 이상, ?는 0 또는 1, +는 1 이상을 의미합니다.
    • 예를 들어, /about/:path*/about/a/b/c 를 포함합니다.
  • 괄호 안에 정규식을 사용할 수 있습니다.
    • /about/(.*)/about/:path*와 같습니다.

Conditional Statements

middleware는 다음과 같이 조건에 따라 동작할 수 있습니다.
중첩 미들웨어를 지원하지 않게 되어 이와 같이 URL에 따라 미들웨어를 실행할 경로를 정의합니다.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
export function middleware(request: NextRequest) {
  // pathname이 '/about'로 시작할 경우 동작하는 코드
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url));
  }
 
  // pathname이 '/dashboard'로 시작할 경우 동작하는 코드
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url));
  }
}

실제 적용 코드

import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import type { NextRequest } from 'next/server';

const secret = process.env.NEXTAUTH_SECRET;

export async function middleware(request: NextRequest) {
  // session 이 있다면 로그인 상태
  const session = await getToken({ req: request, secret, raw: true });
  const { pathname } = request.nextUrl;

  // 로그인 상태에서 로그인, 회원가입 페이지 접근 시 메인 페이지로 리다이렉트
  if (session != null) { // session이 null 또는 undefined 일 수 있음
    if (pathname.startsWith('/account')) {
      return NextResponse.redirect(new URL('/', request.url));
    }
  }
}

export const config = {
  matcher: ['/account/:path*'],
};

현재 next-auth를 이용하여 로그인 기능을 구현했습니다. 로그인에 성공하면 쿠키에 session token이 저장됩니다. 토큰의 유무를 통해 로그인 유무를 판별하고 로그인 상태에 따라 로그인/회원가입 페이지 접근을 제한하였습니다.

마무리

간단하게 Next.js의 middleware를 적용해보았는데 실은 더 많은 기능을 지원합니다.
현재는 로그인 상태일 때 로그인/회원가입 페이지에 접근을 제한했지만, 로그인 상태가 아닌 경우 접근을 제한하거나, 로그인한 사용자가 일반 사용자인지 어드민인지에 따라 특정 페이지 접근에 제한하는 코드를 적용해야합니다.

그래서 대부분의 페이지에 접근 제한을 해야할 거 같은데..

다른 블로그 예시에서 config 없이 접근 가능한 경로를 배열로 만든 상수를 생성하여 적용한 코드가 있었는데, config가 없으면 굉장히 느리거나 코드가 정상적으로 동작하지 않았습니다.
예를 들어 로그인 후 로그인 페이지에 접근하면 메인 페이지로 리다이렉트 되었지만 회원가입 페이지에는 접근할 수 있었습니다.

config 없이 시도해 본 이유는 middleware 내에서 특정 페이지마다 코드르 적용할 것이기 때문에 config를 꼭 작성해야하나..? 싶어서 시도해보았습니다.
⇒ matcher에 일치하는 경로에서 동작하기 때문에 괜한 시도를 한거 같기도.. 허허

추가로 matcher에 /:path*를 적용해보았는데 리다이렉트 횟수가 너무 많아 페이지가 작동하지 않았습니다.

next-auth에서 제공하는 withAuth 미들웨어를 추가로 적용하여 고도화할 예정입니다.
다음에 이어서 내용 작성해보겠습니다!

참고

0개의 댓글