미들웨어는 소프트웨어 시스템에서 요청과 응답 사이에 위치하여 중간 처리를 수행하는 소프트웨어 구성 요소입니다.
일반적으로 클라이언트로부터 요청이 서버에 도달하기 전에 중간에 처리되는 로직을 구현하는데 사용합니다.
예를 들어 인증, 인가, 오류 처리 등 미들웨어로 구현하여 프로그램의 기능을 확장하거나 요청을 처리하는 과정을 변경할 수 있습니다.
미들웨어는 양 쪽을 연결하여 데이터를 주고 받을 수 있도록 중간에서 매개 역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 컴퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해 주는 소프트웨어를 말한다. 3계층 클라이언트/서버 구조에서 미들웨어가 존재한다. 웹 브라우저에서 데이터베이스로부터 데이터를 저장하거나 읽어올 수 있게 중간에 미들웨어가 존재하게 된다.
위키백과 - 미들웨어
예전 개인 프로젝트에서는 로그인을 하면 쿠키에 토큰을 저장하여 저장된 토큰이 있을 경우, 페이지 접근이 가능하고 그렇지 않은 경우 로그인 페이지로 라우팅 처리하는 방식을 이용했습니다.
// js-cookie 라이브러리 사용
// 쿠키에 저장된 token을 가져옴
const token = Cookies.get('token');
useEffect(() => {
// token이 없는 경우 로그인 페이지로 이동
if (!token) {
router.push('/login');
}
}, []);
useEffect
를 통해 토큰 여부를 확인한 후 페이지 이동이 일어나는데, 이 과정에서 해당 페이지가 잠깐 페이지가 노출됩니다.
이를 해결하고자 이번 프로젝트에서는 Next.js에서 제공하는 middleware를 사용하게 되었습니
다. 미들웨어를 설정하면 페이지 노출없이 바로 리다이렉트를 할 수 있습니다.
로그인 한 사용자가 로그인/회원가입 페이지에 접근하지 못하도록 제한하는 코드를 미들웨어에 적용해보았습니다.
미들웨어를 사용하기 위해 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
에는 몇 가지 규칙이 있습니다.
/
로 시작해야 합니다./about/:path
matches는 /about/a
및 /about/b
를 포함할 수 있지만 /about/a/c
는 포함할 수 없습니다.:
기호로 시작해야합니다.*
는 0 이상, ?
는 0 또는 1, +
는 1 이상을 의미합니다./about/:path*
는 /about/a/b/c
를 포함합니다./about/(.*)
는 /about/:path*
와 같습니다.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 미들웨어를 추가로 적용하여 고도화할 예정입니다.
다음에 이어서 내용 작성해보겠습니다!
참고