미들웨어는 요청이 완료되기 전에 코드를 실행할 수 있게 해줍니다. 그런 다음 들어오는 요청에 따라 응답을 재작성(re-writing), 리다이렉션(re-directing), 요청이나 응답 헤더를 수정하거나, 직접 응답할 수 있습니다.
미들웨어는 캐시된 콘텐츠와 라우트가 일치하기 전에 실행됩니다. 더 자세한 내용은 'Matching Paths'를 참고하세요.
Convention
프로젝트의 루트에서 미들웨어를 정의하기 위해 middleware.ts 또는 (.js) 파일을 사용하세요.
예를 들어, pages나 app과 같은 레벨에서, 또는 필요한 경우 src 내부에서 사용할 수 있습니다.
Example
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*',
}
config 객체는 미들웨어의 설정을 정의합니다. matcher 속성은 미들웨어가 적용될 경로를 지정합니다.
이 경우, '/about/:path' 로 지정했고 '/about'으로 시작하는 모든 경로에 미들웨어가 적용됨을 의미합니다.
적용되는 미들웨어의 내용은 request.url 대신 '/home' 페이지로 리다이렉트하는 내용입니다.
Matching Paths
미들웨어는 프로젝트의 모든 라우트에 대해 호출됩니다. 다음은 실행 순서입니다.
1. headers from next.config.js
2. redirects from next.config.js
3. middleware (rewrites, redirects, etc)
4. beforeFiles (rewrites) from next.config.js
5. Filesystem routes (public/, _next/static/, pages/, app/, etc.)
6. afterFiles (rewrites) from next.config.js
7. Dynamic Routes (/blog/[slug])
8. fallback (rewrites) from next.config.js
미들웨어가 실행될 경로를 정의하는 두 가지 방법이 있습니다
1) Custom matcher config
2) Conditional statements
Matcher
matcher는 특정 경로에서 미들웨어를 실행하도록 필터링 할 수 있게 해줍니다.
export const config = {
matcher: '/about/:path*',
}
단일 경로나 배열 문법을 사용하여 여러 경로를 일치시킬 수 있습니다.
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher 설정은 완전한 정규 표현식을 허용하므로, negative lookaheads나 문자 일치와 같은 일치 방식을 지원합니다. 특정 경로를 제외한 모든 것을 일치시키는 negative lookahead의 예는 다음과 같습니다.
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).*)',
],
}
Good to know : matcher 값은 빌드 시간에 정적으로 분석될 수 있도록 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.
Configured matchers:
1. 반드시 /로 시작해야 합니다.
2. 명명된 매개변수를 포함할 수 있습니다 : /about/:path는 /about/a와 /about/b에 일치하지만 /about/a/c에는 일치하지 않습니다.
3. 명명된 매개변수에 수정자를 가질 수 있습니다. (:로 시작) : /about/path*는 /about/a/b/c에 일치합니다. 왜냐하면 *는 0개 이상이기 때문입니다. ?는 0개 또는 1개를 의미합니다. +는 1개 이상을 의미합니다.
4. 괄호로 둘러싼 정규 표현식을 사용할 수 있습니다 : /about/(.)는 /about/:path 와 동일합니다
더 자세한 정보는 path-to-regexp 문서를 참고하십시오.
Good to know : backward 호환성을 위해, Next.js는 항상 /public을 /public/index로 간주합니다.
따라서, /public/:path라는 matcher는 일치하게 됩니다.
Conditional Statements
// middleware.ts
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))
}
}
Good to know : rewrite를 사용하는 이유
1. 구조 변경에 대한 유연성
2. SEO 최적화
3. 보안 강화
4. 단순화된 URL
NextResponse
NextResponse API는 다음을 허용합니다
- 들어오는 요청을 다른 URL로 redirect
- 주어진 URL을 표시함으로써 응답을 rewrite
- API 루트, getServerSideProps, 그리고 rewrite 대상에 대한 요청 헤더 설정
- 응답 쿠키 설정
- 응답 헤더 설정
미들웨어에서 응답을 생성하려면 다음을 수행할 수 있습니다.
Using Cookies
쿠키는 일반 헤더입니다. 요청(Request)에서는 Cookie 헤더에 저장됩니다. 응답(Response)에서는 Set-Cookie 헤더에 있습니다. Next.js는 NextRequest와 NextResponse에서 쿠키 확장을 통해 이러하 쿠키에 편리하게 접근하고 조작할 수 있는 방법을 제공합니다.
- For incoming requests, 쿠키는 다음 메서드와 함께 제공됩니다. get, getAll, set 그리고 delete. 쿠키의 존재 여부는 has로 확인하거나 모든 쿠키를 clear로 제거할 수 있습니다.
- For outgoing responses, 쿠키는 다음 메서드를 가집니다. get, getAll, set 그리고 delete
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
}
Setting Headers
NextResponse API를 사용하여 요청 및 응답 헤더를 설정할 수 있습니다. (v13.0.0 이후 버전부터 가능)
// 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
}
Good to know : 백엔드 웹 서버 구성에 따라 큰 헤더를 설정하면 431 Request Header Fields Too Large 오류가 발생할 수 있으므로 피하는 것이 좋습니다.
Producing a Response
Middleware에서 직접 Response 또는 NextResponse 인스턴스를 반환하여 응답할 수 있습니다. (v13.1.0 이후 버전부터 가능)
import { NextRequest, NextResponse } 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 new NextResponse(
JSON.stringify({ success: false, message: 'authentication failed' }),
{ status: 401, headers: { 'content-type': 'application/json' } }
)
}
}
Advanced Middleware Flags
Next.js의 v13.1 버전에서 두 가지 추가 플래그인, skipMiddlewareUrlNormalize와 skipTrailingSlashRedirect가 고급 사용 사례를 처리하기 위해 도입되었습니다.
skipTrailingSlashRedirect는 Next.js의 기본 리다이렉트를 사용하지 않고 끝에 슬래시를 추가하거나 제거할 수 있게 하여 미들웨어 내에서 커스텀 처리가 가능합니다. 이로써 일부 경로에는 끝에 슬래시를 유지하고 다른 경로에는 끝에 슬래시를 사용하지 않게 하여 더 쉬운 점진적 마이그레이션을 가능하게 할 수 있습니다.
여기서 skipMiddlewareUrlNormalize와 skipTrailingSlashRedirect는 미들웨어의 동작을 조정하는 플래그로, 특정 경우에 URL 정규화나 끝에 오는 슬래시의 리다이렉트를 건너뛰게 해줍니다. 이러한 기능은 복잡한 라우팅 요구사항을 처리할 때 유용할 수 있습니다.
// next.config.js
module.exports = {
skipTrailingSlashRedirect: true,
}
// middleware.js
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을 사용하여 완전히 제어해야 하는 일부 고급 사례를 해결할 수 있습니다.
이 플래그는 원본 URL을 그대로 사용해야 하는 특수한 상황에서 유용하게 사용될 수 있으며, Next.js의 일반적인 URL 처리를 건너뛰고 개발자가 완전한 제어를 갖게 해줍니다.
// next.config.js
module.exports = {
skipMiddlewareUrlNormalize: true,
}
// middleware.js
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
}
Good to know : skipMiddlewareUrlNormalize는 URL 정규화 작업과 관련이 있다.
이는 다음과 같은 상황에서 유용하게 쓰일 수 있다.
1. 웹 사이트의 URL은 대소문자를 구분하지 않으며, 모든 요청을 소문자로 처리하려고 할 때.
2. 웹 사이트의 URL 중간에 불필요한 슬래시(/)를 제거하려고 할 때
3. SEO 최적화를 위해 URL의 쿼리 파라미터의 순서를 통일하려고 할 때
4. www 제거 또는 추가를 통해 'www'의 유무와 관계없이 동일한 내용을 제공하려고 할 때
Version History