- Published on
NextJs 13 이후 - Middleware
- Authors
- Name
- piano cat
미들웨어를 사용하면 요청이 완료되기전에 코드를 실행시킬 수 있다. 그런다음 들어오는 요청에 따라 요청 또는 응답 헤더를 다시작성, 리디렉션, 수정하거나 직접 응답하여 응답을 수정할 수 있다. 미들웨어는 캐시된 컨텐츠와 경로가 일치하기 전에 실행된다.
Convention
미들웨어를 정의하려면 프로젝트 루트 경로에 middleware.ts
를 사용하여 작성한다.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
Matching paths
미들웨어는 프로젝트의 모든 경로에 대해 호출된다. 실행순서는 다음와 같다.
headers
fromnext.config.js
redirects
fromnext.config.js
- Middleware (
rewrites
,redirects
, etc.) beforeFiles
(rewrites
) fromnext.config.js
- Filesystem routes (
public/
,_next/static/
,pages/
,app/
, etc.) afterFiles
(rewrites
) fromnext.config.js
- Dynamic Routes (
/blog/[slug]
) fallback
(rewrites
) fromnext.config.js
미들웨어를 실행할수 있는 방법은 두가지로 정의된다.
Matcher
matcher 변수를 설정하여 미들웨어를 실행시키는 경로를 설정할 수 있다.
// middleware.ts
export const config = {
matcher: '/about/:path*',
}
배열을 사용하여 여러 경로를 설정가능하다.
// middleware.ts
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
또한 matcher 에서는 전체 정규식을 허용하므로 부정 예측 또는 문자 일치와 같은 일치가 지원된다. 특정 경로를 제외한 모든 경로와 일치하는 부정예측의 예는 아래와 같다.
// middleware.ts
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
matcher 값은 동적 값이 아닌 상수여야한다. 변수와 같은 동적 값은 무시된다.
matcher 적용 방식
/
으로 시작되어야한다./about/:path
로 설정하면,/about/a
,/about/b
는 포함되며,/about/a/c
는 포함되지않는다./about/:path*
으로 설정하면about/a/c
를 포함한다.*
는 zero or more,?
는 zero or one,+
는 one or more/about/(.*)
은/about/:path*
와 같다.
조건문 형식
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
NextResponse
API 는 아래와 같이 쓰인다.
- 요청 들어온경로에서 다른 URL으로 리다이렉트 한다.
- 전달된 URL을 재작성하여 응답한다.
- 응답 쿠키 설정
- 응답 헤더 설정
미들웨어에서 응답을 생성하려면 다음을 수행할 수 있다.
- 응답을 생성하는 경로를 재작성한다. (page, Route Handler)
- NextResponse 로 직접 반환한다.
쿠키 사용
쿠키는 일반 헤더이다. 요청안에 저장된 쿠키 헤더가 있다. 응답안에는 set-Cookie 헤더가 있다. NextJs는 NextRequest
NextResponse
를 사용하여 이러한 쿠키에 접근하고 조작하는 편리한 방법을 제공한다.
- 들어온 요청에서
get
,getAll
,set
, anddelete
cookies. 등의 메서드를 사용하여 쿠키를 확인하거나 제거 등을 조작할 수 있다. - 나가는 응답에선
get
,getAll
,set
, anddelete
등의 메서드를 사용할 수있다.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Setting cookies on the response using the `ResponseCookies` API
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.
return response
}
헤더 설정
NextResponse
API를 사용하여 요청 그리고 응답헤더를 세팅할 수 있다. (nextJS 13이후 가능)
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
응답 생성
Response
또는 NextResponse
인스턴스를 반환하여 미들웨어에서 직접 응답 할 수도 있다. (NextJs 13 이후 가능)
// middleware.ts
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json({ success: false, message: 'authentication failed' }, { status: 401 })
}
}
고급 미들웨어 플래그
skipMiddlewareUrlNormalize
, skipTrailingSlashRedirect
플래그를 사용하여 고급 미들웨어를 구현할 수 있다.
skipTrailingSlashRedirect
는 후행 슬래시를 추가하거나 제거하기 위해 nextJS 기본 리다이렉션을 비활성화 할수 있으므로, 미들웨어 내에서 사용자 지정처리가 가능해 일부 경로에 대해서는 후행 슬래시를 유지할 수 있지만 다른 경로에서는 유지 관리가 불가능하므로 증분 마이그레이션이 더 쉬워진다.
// next.config.js
module.exports = {
skipTrailingSlashRedirect: true,
}
// middleware.ts
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalize
Next.js가 수행하는 URL 정규화를 비활성화하여 직접 방문과 클라이언트 전환을 동일하게 처리할 수 있다. 잠금 해제되는 원래 URL을 사용하여 완전한 제어가 필요한 몇 가지 고급 사례가 있다.
// next.config.js
module.exports = {
skipMiddlewareUrlNormalize: true,
}
// middleware.ts
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}