Middleware
Middleware를 사용하면 요청이 완료되기 전에 코드를 실행할 수 있습니다. 그런 다음, 들어오는 요청에 따라 응답을 재작성, 리디렉션, 요청 또는 응답 헤더 수정, 또는 직접 응답할 수 있습니다.
Middleware는 캐시된 콘텐츠와 라우트가 일치하기 전에 실행됩니다. 자세한 내용은 Matching Paths를 참조하세요.
Use Cases
애플리케이션에 Middleware를 통합하면 성능, 보안 및 사용자 경험이 크게 향상될 수 있습니다. Middleware가 특히 효과적인 일반적인 시나리오는 다음과 같습니다:
- 인증 및 권한 부여: 사용자 신원을 확인하고 특정 페이지나 API 라우트에 접근하기 전에 세션 쿠키를 확인합니다.
- 서버 측 리디렉션: 특정 조건(예: 로케일, 사용자 역할)에 따라 서버 수준에서 사용자를 리디렉션합니다.
- 경로 재작성: 요청 속성에 따라 경로를 동적으로 재작성하여 A/B 테스트, 기능 출시 또는 레거시 경로를 지원합니다.
- 봇 감지: 리소스를 보호하기 위해 봇 트래픽을 감지하고 차단합니다.
- 로깅 및 분석: 페이지나 API가 처리되기 전에 요청 데이터를 캡처하고 분석합니다.
- 기능 플래깅: 기능을 동적으로 활성화 또는 비활성화하여 원활한 기능 출시 또는 테스트를 진행합니다.
Middleware가 최적의 접근 방식이 아닌 상황을 인식하는 것도 중요합니다. 주의해야 할 몇 가지 시나리오는 다음과 같습니다:
- 복잡한 데이터 가져오기 및 조작: Middleware는 직접적인 데이터 가져오기나 조작을 위해 설계되지 않았으며, 이는 Route Handlers 또는 서버 측 유틸리티 내에서 수행해야 합니다.
- 무거운 계산 작업: Middleware는 가볍고 빠르게 응답해야 하며, 무거운 계산 작업이나 장기 실행 프로세스는 전용 Route Handlers 내에서 수행해야 합니다.
- 광범위한 세션 관리: Middleware는 기본적인 세션 작업을 관리할 수 있지만, 광범위한 세션 관리는 전용 인증 서비스 또는 Route Handlers 내에서 관리해야 합니다.
- 직접적인 데이터베이스 작업: Middleware 내에서 직접적인 데이터베이스 작업을 수행하는 것은 권장되지 않습니다. 데이터베이스 상호작용은 Route Handlers 또는 서버 측 유틸리티 내에서 수행해야 합니다.
Convention
프로젝트의 루트에 middleware.ts
(또는 .js
) 파일을 사용하여 Middleware를 정의합니다. 예를 들어, pages
또는 app
과 같은 수준에 있거나 src
내에 있을 수 있습니다.
Note: 프로젝트당 하나의
middleware.ts
파일만 지원되지만, 여전히 모듈화된 방식으로 미들웨어 로직을 구성할 수 있습니다. 미들웨어 기능을 별도의.ts
또는.js
파일로 분리하고 주요middleware.ts
파일에 가져올 수 있습니다. 이렇게 하면 경로별 미들웨어를 보다 깔끔하게 관리할 수 있으며, 중앙 집중식 제어를 위해middleware.ts
에 집계할 수 있습니다. 단일 미들웨어 파일을 강제함으로써 구성의 간소화, 잠재적 충돌 방지 및 여러 미들웨어 계층을 피하여 성능 최적화를 달성할 수 있습니다.
Example
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// 이 함수는 내부에서 `await`를 사용하는 경우 `async`로 표시될 수 있습니다
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// 아래 "Matching Paths"를 참조하여 자세히 알아보세요
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// 이 함수는 내부에서 `await`를 사용하는 경우 `async`로 표시될 수 있습니다
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// 아래 "Matching Paths"를 참조하여 자세히 알아보세요
export const config = {
matcher: '/about/:path*',
}
Matching Paths
Middleware는 프로젝트의 모든 라우트에 대해 호출됩니다. 따라서, 특정 라우트를 정확히 타겟팅하거나 제외하기 위해 매처를 사용하는 것이 중요합니다. 실행 순서는 다음과 같습니다:
next.config.js
의headers
next.config.js
의redirects
- Middleware (
rewrites
,redirects
등) next.config.js
의beforeFiles
(rewrites
)- 파일 시스템 라우트 (
public/
,_next/static/
,pages/
,app/
등) next.config.js
의afterFiles
(rewrites
)- 동적 라우트 (
/blog/[slug]
) next.config.js
의fallback
(rewrites
)
Middleware가 실행될 경로를 정의하는 두 가지 방법이 있습니다:
Matcher
matcher
를 사용하여 특정 경로에서만 Middleware를 실행하도록 필터링할 수 있습니다.
export const config = {
matcher: '/about/:path*',
}
단일 경로나 여러 경로를 배열 구문으로 매칭할 수 있습니다:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher
구성은 전체 정규식을 허용하므로, 부정형 전방탐색이나 문자 매칭과 같은 정규식 매칭이 지원됩니다. 특정 경로를 제외하고 모든 경로를 매칭하는 부정형 전방탐색의 예는 다음과 같습니다:
export const config = {
matcher: [
/*
* 다음으로 시작하는 경로를 제외한 모든 요청 경로를 매칭합니다:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
또한 missing
또는 has
배열 또는 두 가지 조합을 사용하여 특정 요청을 위해 Middleware를 우회할 수 있습니다:
export const config = {
matcher: [
/*
* 다음으로 시작하는 경로를 제외한 모든 요청 경로를 매칭합니다:
* - api (API 라우트)
* - _next/static (정적 파일)
* - _next/image (이미지 최적화 파일)
* - favicon.ico (파비콘 파일)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
알아두면 좋은 점:
matcher
값은 빌드 시 정적으로 분석될 수 있도록 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.
구성된 매처:
/
로 시작해야 합니다- 명명된 매개변수를 포함할 수 있습니다:
/about/:path
는/about/a
및/about/b
와 일치하지만/about/a/c
와는 일치하지 않습니다 - 명명된 매개변수(
:
로 시작)에 수정자를 가질 수 있습니다:/about/:path*
는*
이 0개 이상이므로/about/a/b/c
와 일치합니다.?
는 0개 또는 1개,+
는 1개 이상 - 괄호로 둘러싸인 정규식을 사용할 수 있습니다:
/about/(.*)
는/about/:path*
와 동일합니다
자세한 내용은 path-to-regexp (opens in a new tab)
- 문서를 참조하세요.
알아두면 좋은 점: 이전 버전과의 호환성을 위해 Next.js는 항상
/public
을/public/index
로 간주합니다. 따라서/public/:path
의 매처는 일치합니다.
Conditional Statements
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))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
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로
redirect
- 주어진 URL을 표시하여 응답을
rewrite
- API Routes,
getServerSideProps
, 및rewrite
대상에 대해 요청 헤더 설정 - 응답 쿠키 설정
- 응답 헤더 설정
Middleware에서 응답을 생성하려면 다음을 수행할 수 있습니다:
- 응답을 생성하는 라우트 (Page 또는 Route Handler)로
rewrite
NextResponse
를 직접 반환합니다. Producing a Response를 참조하세요
Using Cookies
쿠키는 일반 헤더입니다. Request
에서는 Cookie
헤더에 저장됩니다. Response
에서는 Set-Cookie
헤더에 저장됩니다. Next.js는 NextRequest
와 NextResponse
에서 쿠키에 쉽게 접근하고 조작할 수 있는 편리한 방법을 제공합니다.
- 들어오는 요청의 경우,
cookies
는 다음 메서드를 제공합니다:get
,getAll
,set
, 및delete
쿠키.has
를 사용하여 쿠키의 존재 여부를 확인하거나clear
를 사용하여 모든 쿠키를 제거할 수 있습니다. - 나가는 응답의 경우,
cookies
는 다음 메서드를 제공합니다:get
,getAll
,set
, 및delete
.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 들어오는 요청에 "Cookie:nextjs=fast" 헤더가 있다고 가정합니다
// `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
// `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: '/' }
// 나가는 응답에는 `Set-Cookie:vercel=fast;path=/` 헤더가 포함됩니다.
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// 들어오는 요청에 "Cookie:nextjs=fast" 헤더가 있다고 가정합니다
// `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
// `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: '/' }
// 나가는 응답에는 `Set-Cookie:vercel=fast;path=/test` 헤더가 포함됩니다.
return response
}
Setting Headers
NextResponse
API를 사용하여 요청 및 응답 헤더를 설정할 수 있습니다 (요청 헤더 설정은 Next.js v13.0.0부터 사용할 수 있습니다).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 요청 헤더를 복제하고 새로운 헤더 `x-hello-from-middleware1`을 설정합니다
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// NextResponse.next에서 요청 헤더를 설정할 수도 있습니다
const response = NextResponse.next({
request: {
// 새로운 요청 헤더
headers: requestHeaders,
},
})
// 새로운 응답 헤더 `x-hello-from-middleware2`를 설정합니다
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// 요청 헤더를 복제하고 새로운 헤더 `x-hello-from-middleware1`을 설정합니다
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// NextResponse.next에서 요청 헤더를 설정할 수도 있습니다
const response = NextResponse.next({
request: {
// 새로운 요청 헤더
headers: requestHeaders,
},
})
// 새로운 응답 헤더 `x-hello-from-middleware2`를 설정합니다
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
알아두면 좋은 점: 큰 헤더를 설정하면 백엔드 웹 서버 구성에 따라 431 Request Header Fields Too Large (opens in a new tab) 오류가 발생할 수 있으므로 피해야 합니다.
CORS
Middleware에서 CORS 헤더를 설정하여 교차 출처 요청을 허용할 수 있으며, 여기에는 단순 요청 (opens in a new tab) 및 사전 요청 (opens in a new tab)이 포함됩니다.
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// 요청의 origin을 확인합니다
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// 사전 요청을 처리합니다
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// 단순 요청을 처리합니다
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// 요청의 origin을 확인합니다
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// 사전 요청을 처리합니다
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// 단순 요청을 처리합니다
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
알아두면 좋은 점: Route Handlers에서 개별 라우트에 대한 CORS 헤더를 구성할 수 있습니다.
Producing a Response
Middleware에서 Response
또는 NextResponse
인스턴스를 반환하여 직접 응답할 수 있습니다. (이는 Next.js v13.1.0 (opens in a new tab)부터 사용 가능합니다)
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Middleware를 `/api/`로 시작하는 경로로 제한합니다
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// 요청을 확인하기 위해 인증 함수를 호출합니다
if (!isAuthenticated(request)) {
// 오류 메시지를 나타내는 JSON으로 응답합니다
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 },
)
}
}
import { isAuthenticated } from '@lib/auth'
// Middleware를 `/api/`로 시작하는 경로로 제한합니다
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// 요청을 확인하기 위해 인증 함수를 호출합니다
if (!isAuthenticated(request)) {
// 오류 메시지를 나타내는 JSON으로 응답합니다
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 },
)
}
}
waitUntil
and NextFetchEvent
NextFetchEvent
객체는 기본 FetchEvent
(opens in a new tab) 객체를 확장하며 waitUntil()
(opens in a new tab) 메서드를 포함합니다.
waitUntil()
메서드는 프라미스를 인수로 받아서 프라미스가 해결될 때까지 Middleware의 수명을 연장합니다. 이는 백그라운드에서 작업을 수행하는 데 유용합니다.
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
}),
)
return NextResponse.next()
}
Advanced Middleware Flags
Next.js v13.1에서 두 가지 추가 플래그, skipMiddlewareUrlNormalize
및 skipTrailingSlashRedirect
가 도입되어 고급 사용 사례를 처리할 수 있습니다.
skipTrailingSlashRedirect
는 후행 슬래시를 추가하거나 제거하기 위한 Next.js 리디렉션을 비활성화합니다. 이를 통해 일부 경로에서는 후행 슬래시를 유지하고 다른 경로에서는 유지하지 않도록 커스텀 처리를 수행할 수 있어 점진적인 마이그레이션을 용이하게 합니다.
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// 후행 슬래시 처리 적용
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, req.nextUrl),
)
}
}
skipMiddlewareUrlNormalize
는 Next.js에서 URL 정규화를 비활성화하여 직접 방문과 클라이언트 전환을 동일하게 처리할 수 있게 합니다. 일부 고급 사례에서는 이 옵션을 통해 원래 URL을 사용하여 완전한 제어를 제공합니다.
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// 플래그 사용 시 /_next/data/build-id/hello.json
// 플래그 사용 안 할 경우 /hello로 정규화됩니다
}
Runtime
Middleware는 현재 Edge runtime만 지원합니다. Node.js 런타임은 사용할 수 없습니다.
Version History
Version | Changes |
---|---|
v13.1.0 | Advanced Middleware flags added |
v13.0.0 | Middleware can modify request headers, response headers, and send responses |
v12.2.0 | Middleware is stable, please see the upgrade guide |
v12.0.9 | Enforce absolute URLs in Edge Runtime (PR (opens in a new tab)) |
v12.0.0 | Middleware (Beta) added |