Next.js 미들웨어를 통해 로그인 페이지에 접근 시 토큰이 유효하다면 대시보드로 리다이렉트 시키고, 토큰이 만료됐다면 그대로 로그인 페이지로 접속되게 하는 로직을 구현하려고 했다.
export async function middleware(request: NextRequest) {
const AUTH_ROUTES: string[] = ['/login']
const pathname = request.nextUrl.pathname
if (AUTH_ROUTES.includes(pathname)) {
// 토큰 검증 및 리다이렉트 로직
}
}
pathname
에 현재 경로를 저장한 후 해당 경로가 AUTH_ROUTES
배열에서 지정한 경로에 해당되면 토큰 검증 및 리다이렉션 로직을 처리하도록 구현했다.SyntaxError: Unexpected token '<'
)가 발생한다.webpack.js?v=1737017745486:1 Uncaught SyntaxError: Unexpected token '<' (at webpack.js?v=1737017745486:1:1)
page.js:1 Uncaught SyntaxError: Unexpected token '<' (at page.js:1:1)
layout.js:1 Uncaught SyntaxError: Unexpected token '<' (at layout.js:1:1)
원인은 Next.js 미들웨어의 작동 방식에 있다.
지정한 페이지(AUTH_ROUTES
)에 /login 페이지가 있었고, 이 페이지에 접근했을 때,pathname
에 /login
뿐만 아니라 여러 정적 파일 경로들이 넘어오고 있었다.
<console.log(pathname)> 결과
/_next/static/css/app/layout.css
/_next/static/chunks/webpack.js
/_next/static/chunks/main-app.js
/_next/static/chunks/app-pages-internals.js
/_next/static/chunks/app/(auth)/login/page.js
/_next/static/chunks/app/(auth)/login/layout.js
/login
여기서 중요한 사실은 미들웨어는 프로젝트의 모든 경로(/_next/static/...
, /favicon.ico
, /login
등)에 대해 실행된다는 것이다. 따라서, 명시적으로 제외하지 않으면 정적 파일 요청도 동일한 미들웨어 로직이 적용된다.
관련 내용: Routing: Middleware
AUTH_ROUTES
배열은 /login
경로를 허용하는데, 정적 파일 요청은 AUTH_ROUTES
에 포함되지 않으므로 아래 코드의 로직에 의하여 기본적으로 정적 파일 요청들이 "허용되지 않는 것처럼" 동작하게 된다.
if (AUTH_ROUTES.includes(pathname)) {
// 토큰 검증 및 리다이렉트 로직
}
결과적으로, /login
에 필요한 CSS
와 JS
등의 정적 리소스 요청이 차단되어 페이지가 제대로 렌더링되지 않는다.
SyntaxError: Unexpected token
에러의 원인그렇다면 브라우저의 개발자 도구 Console에 나타난 SyntaxError: Unexpected token
에러는 왜 발생한 걸까?
SyntaxError: Unexpected token
이 에러는 브라우저가 자바스크립트를 로드하려고 했는데, 예상치 못한 데이터(오타 등)를 받았을 때 발생한다. 현재 상황에서는 브라우저가 정적 리소스 파일(예: .js
나 .css
)을 요청했지만, 미들웨어가 제대로 처리하지 못해 HTML 페이지를 반환한 경우에 발생한다.
/login
페이지를 렌더링하기 위해 필요한 CSS와 JavaScript를 요청했다./_next/static/css/app/layout.css
와 같은 파일들./_next/static/...
)도 AUTH_ROUTES
조건을 통과하려고 했다.AUTH_ROUTES
에 정적 리소스 경로가 포함되지 않으므로, 미들웨어는 /login
과 같은 동적 페이지 요청처럼 이를 처리하려 했다.<
문자(HTML의 시작)를 만났고, "예상치 못한 토큰 '<'"이라는 에러를 던졌다.결론적으로, 브라우저는 정적 리소스를 요청했는데, 미들웨어 때문에 JavaScript 파일 대신 HTML 파일을 받았다. 하지만 자바스크립트가 아니기 때문에 에러가 발생한 것이다.
문제를 해결하는 방법은 정적 리소스 요청을 미들웨어 처리에서 제외하는 것이다.
pathname.match
)정적 파일 요청은 일반적으로 확장자를 포함하므로, 파일 요청인지 정규식으로 확인할 수 있다.
// 경로에 확장자가 있으면 `true` 없으면 `null`
const isFileRequest = pathname.match(/\.\w+$/);
// 경로에 확장자가 있다면 미들웨어 로직 스킵
if (isFileRequest) {
return NextResponse.next();
}
\.\w+$
는 .
으로 시작하는 파일 확장자를 포함한 요청을 감지한다./videos/sample.mp4
, /_next/static/css/app.css
, /images/logo.png
isFileRequest
가 true
(정적 파일 요청)일 때/static/js/main.js
, /images/logo.png
같은 정적 파일 요청이 정상적으로 렌더링될 수 있도록 보장한다.isFileRequest
가 null
(정적 파일 요청이 아님)일 때/login
, /dashboard
)이므로 미들웨어의 나머지 로직(토큰 검증 및 리다이렉션 로직)을 실행한다.export async function middleware(request: NextRequest) {
const AUTH_ROUTES: string[] = ['/login']
const isFileRequest = request.nextUrl.pathname.match(/\.(.*)$/)
const pathname = request.nextUrl.pathname
if (isFileRequest) {
return NextResponse.next()
}
if (AUTH_ROUTES.includes(pathname)) {
// 토큰 검증 및 리다이렉트 로직
}
}
middleware의 matcher속성을 사용하지 않는 이유가 있으실까요??
export const config = {
matcher: ['/login']
}