현재 프로젝트에서 NextJS13 버전을 사용중이다.
NextJS의 이전 버전을 사용해본 적이 없어서 얼마나 많은 차이가 있는지는 모르겠다.
NextJS13의 특징을 간략히 정리하면 다음과 같다.
- server-component와 client-component
- app router
- fetch 함수를 이용한 SSR, ISR 등의 구현
따라서, 이 프로젝트를 진행하며 app router를 사용하고 axios 대신 fetch 함수를 사용하고자 노력했다.
1. 문제상황
현재 프로젝트의 경우는 별도의 백엔드 서버가 있고 DB관련 연산은 모두 백엔드 서버에서 수행한다.
백엔드 서버는 API를 제공하므로 NextJS를 이용해 DB 등에 접근할 일은 없었다.
인증 방식으로는 JWT를 사용했으며 JWT의 정보를 쿠키에 담아 저장하도록 했다.
서버 컴포넌트와 클라이언트 컴포넌트가 JWT 데이터를 공유할 수 있는 방법이 마땅히 떠오르지 않았으며 쿠키에 넣고 필요할 때 꺼내서 사용할 수 있도록 구현했다.
(적절한 방법인지는 확실치 않다. 서버 컴포넌트와 클라이언트 컴포넌트는 차이가 있으며 클라이언트 컴포넌트에서 사용 가능한 useState 등의 react 문법과 context 등을 서버 컴포넌트에서 사용하려 하면 에러가 발생한다)
쿠키를 이용한 통신을 할 때, access_token의 유효기간을 짧게 설정하고 refresh_token을 이용해 access_token을 갱신할 수 있도록 구성하려 했다.
처음에는 각 fetch 함수들에 토큰 리프레시 기능을 넣었지만 다른 방법이 없는지 찾아보던 중 공식문서에서 Middleware에 대해 알게 되었다.
2. Middleware
Routing: Middleware | Next.js (nextjs.org)
미들웨어는 쉽게 생각해서 "특정 URL로 접근할 때, 해당 요청 이전에 수행하는 함수"이다.
예를 들어 www.test.com/example로 접근한다면 해당 요청에 대한 request 정보를 받아올 수 있으며 middleware에서 request가 완료되기 전에 특정 작업을 수행할 수 있다.
이번 프로젝트의 경우 middleware를 이용해 "access_token이 없다면 refresh_token을 이용해 재발급 + refresh_token도 없다면 /login으로 리다이렉션" 기능을 만들었다.
middleware 파일은 일정한 규칙을 가지고 있다.
(app 디렉토리를 이용하는 경우) app 디렉토리와 같은 디렉토리 (루트 디렉토리 또는 src 디렉토리)에 middleware.ts 또는 middleware.js를 생성한다.
해당 파일 안의 내용에는 아래와 같은 함수를 생성한다. (async를 함께 작성해도 된다)
export function middleware(request: NextRequest) { ... }
request에 접근하면 현재 가로챈 요청의 정보를 볼 수 있다.
또한, 특정 경로로 접근할 때 middleware를 사용하기 위해서는 아래와 같이 작성한다.
export const config = {
matcher: ['/login', '/wiki/:path*', '/w/all', '/edit/:path*'],
};
matcher에는 정규식도 사용 가능하다.
위의 경우 /login, /wiki의 하위 모든 경로, w/all, /edit의 하위 모든 경로에 해당하는 경우는 middleware가 요청을 가로채게 된다.
위의 방식으로 작성하면 요청이 어느 주소로 접근하는지 확인할 수 없다.
(위의 경로에 해당하는 모든 요청을 가로채기 때문)
따라서, 주소마다 분기하기 위해서는 아래와 같이 작성할 수 있다.
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/login') { ... }
}
request.nextUrl.pathname으로 접근하면 요청의 pathname 문자열을 알 수 있다.
위의 코드는 /login으로 요청이 들어온 경우에만 실행될 것이다.
3. 응용
위의 예제에서 middleware를 이용해 어디로 향하는 요청인지 확인할 수 있었다.
중요한 것은 middleware도 NextJS의 route handler 처럼 NextResponse를 return할 수 있다는 것이다.
이를 이용해 set-cookie, redirect, rewrite 등의 응답을 보낼 수 있다.
만약, "/w/all로 접근하는 상황에서 access_token이 없는 경우, refresh_token을 이용해 새로운 access_token을 발급받고 이를 이용해 다시 w/all로 접근"한다면 어떻게 구현할 수 있는가?
(클라이언트) -> /w/all의 상황에서 middleware가 추가된다면 (클라이언트) -> (middleware) -> /w/all이 될 것이다.
middleware에서 새로운 access_token을 발급받고 NextResponse를 이용해 응답한다면 흐름은 다음과 같을 것이다.
(클라이언트) -> (middleware: set-cookie + redirect) -> (클라이언트) -> /w/all
위에서 사용했던 request.nextUrl.pathname을 사용한다면 처음 요청을 보냈던 주소로 redirect 시킬 수 있을 것이다.
export async function middleware(request: NextRequest) {
if (!request.cookies.has('access_token')) {
// access_token 갱신 과정
const response = NextResponse.redirect(new URL(request.nextUrl.pathname, request.url));
response.cookies.set({ ... });
return response;
}
}
4. 여담
Functions: NextResponse | Next.js (nextjs.org)
import { NextResponse } from 'next/server'
return NextResponse.redirect(new URL('/new', request.url))
NextResponse에 해당하는 공식문서이다.
set-cookie를 이용해 쿠키 설정을 하고 특정 위치로 redirect 시키고 싶었다.
공식문서나 다른 블로그를 참고했을 때 모두 NextResponse.redirect를 return하도록 작성해서 당연히 그렇게 해야하는 줄 알았다.
이렇게 작성하게 되면 set-cookie 또는 redirect 둘 중 하나밖에 사용할 수 없는 문제점이 있다.
하지만 위의 예제에서 볼 수 있듯이 NextResponse.redirect를 변수로 지정하고 추가적인 작업이 가능하다.
아래와 같이 작성한다면 redirect와 set-cookie 등을 동시에 할 수 있다.
const response = NextResponse.redirect(new URL(request.nextUrl.pathname, request.url));
response.cookies.set({ ... });
return response;
Unable to set cookie before redirect · Issue #32424 · vercel/next.js (github.com)
'개발 > TMI' 카테고리의 다른 글
[TypeScript + Jest] TypeScript 프로젝트 시작 + Jest 적용 (0) | 2023.08.06 |
---|---|
[SvelteKit] 스벨트 킷에 테일윈드CSS 적용하기 (SvelteKit + tailwindCSS) (0) | 2023.03.13 |
[Python] copy와 deepcopy (0) | 2023.02.06 |
제2회 너디너리 (Ne(O)rdinary) 해커톤 후기 (0) | 2022.09.26 |