반응형
13. Middleware
- Middleware는 요청이 완료되기 전에 코드를 실행시킬 수 있는 환경을 제공한다.
- 사용자 요청을 기반으로 응답을 재작성하거나, request or response header를 수정하거나 바로 응답하는 등의 작업이 가능하다.
- Caching Content와 Route가 발생하기 전에 실행된다.
13-1. Use Cases
- 사용하면 좋은 경우.
- 인증 및 인가 : Route handler or Page에 접근하기 전에 session cookies를 확인하여 사용자 신원에 대한 인증 및 인가.
- Server Side Redirect : Server Level에서 조건에 따라 사용자를 Redirect.
- Path 재생성 : A/B Test, 신규 기능 적용, legacy path 변경 등 요청 정보에 따라 기존 Path 정보를 재생성.
- Bot 검출 : bot traffic을 막고 검출.
- 로깅 및 분석 : 요청 데이터를 캡쳐해서 로그를 남기거나 분석할 수 있다.
- 기능 on/off : 신규 기능이나 테스트 시 특정 기능의 활성, 비활성화 지정.
- 사용할 때 주의해야 하는 경우.
- 복잡한 데이터 Fetch와 조작 : Middleware는 해당 목적으로 설계되지 않았으므로 Route Handler or Server utilities을 사용.
- 복잡한 계산 로직 : Middleware는 가볍고 빠르게 응답하지 않으면 page load에 딜레이가 생긴다. Route Handler 추천.
- 광범위한 세션 관리 : Middleware는 기본적인 세션 관리가 가능하지만, 해당 목적으로는 별도의 서비스를 사용하거나 Route Handler 내에서 이루어져야 한다.
- 직접적인 DB 조작 : Middleware에서 DB 조작은 추천되지 않는다. Route Handler나 Server utilities를 사용.
- middleware.ts | js 파일을 Project root 경로에 정의할 수 있으며, 관련 로직을 별도로 분리하여 import해서 사용해야 충돌을 피하고 성능을 유지할 수 있다.
13-2. 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*',
}
13-3. Matching Paths
- Project 내 모든 Route에 대해 Middleware가 실행 되기 때문에, 원하는 경로를 타겟팅하고 제외하는 것이 중요.
- 실행 순서는 다음과 같다.
- next.config.js의 headers
- next.config.js의 redirects
- Middleware
- next.config.js의 beforeFiles
- File System Route
- next.config.js의 afterFiles
- Dynamic Route
- next.config.js의 fallback(rewrites)
- Middleware에서 실행될 경로를 정의하는 2가지 방법.
1. Matcher
export const config = { // 단일 Matcher
matcher: '/about/:path*',
}
export const config = { // Multiple Matcher
matcher: ['/about/:path*', '/dashboard/:path*'],
}
export const config = { // 정규표현식 Matcher
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).*)',
],
}
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)
*/
{
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는 Middleware에서 특정 경로를 필터링할 수 있게 해준다.
- 고급 정규표현식을 포함해서 Matcher를 설정할 수도 있다.
- matcher 정보는 Build time에 분석되므로 동적으로 작성하면 안된다.
- missing, has 배열 정보로 특정 조건을 걸 수도 있다.
- missing: 특정 key가 없으면 Middleware를 실행한다.
- has: 특정 key가 있으면 Middleware를 실행한다.
1. matcher는 / 로 시작해야 한다.
2. /about/:path -> /about/a, /about/b 등 매칭, /about/v/c는 매칭 X.
3. /about/:path* -> /about/a, /about/b, /about/v/c 등 매칭.
4. /about/(.*) == /about/:path*
2. 조건문 활용
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))
}
}
13-4. NextResponse
- NextResponse가 제공하는 주요 기능
- 요청과 다른 URL로 redirect.
- 주어진 URL로 응답하는 rewrite.
- Route Handler, getServerSideProps, rewrite 목적지를 위한 request header 정보 추가.
- 응답 쿠키 추가.
- 응답 헤더 추가.
- NextResponse에서 보틍 응답을 생성하기 위해선,
- page or Route Handler로의 rewrite 응답.
- NextResponse를 직접 생성하여 응답.
13-5. Cookies 사용
- Cookies는 일반 헤더로, Request 객체에 저장되어 있으며 Response에 Cookie를 Set할 수 있다.
- NextRequest와 NextResponse의 cookies로 손쉽게 Cookies에 대한 조작이 가능하게 한다.
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=/` header.
return response
}
- 요청 Request : get, getAll, delete, has, clear 등의 함수 제공.
- 응답 Response : get, getAll, set, delete 등의 함수 제공.
13-6. Setting Headers
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
}
- NextResponse API를 이용해서 요청 및 응답 객체에 header 정보를 추가할 수 있다.
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) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
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*',
}
- CORS의 Simple Request, Preflighted Request 관련 header 정보 설정도 가능하다.
13-7. Producing a Response
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 }
)
}
}
- Middleware에서 Response or NextResponse 객체를 직접 반환할 수 있다.
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()
}
- NextFetchEvent는 기존 FetchEvent를 확장하여 waitUntil() 함수가 포함된 객체로, middleware의 인자로 전달된다.
- waitUntil() : 인자로 Promise 객체를 받고, Middleware를 백 그라운드에서 Promise가 처리될 때 까지 실행시켜둘 수 있다.
처음 뵙겠습니다.
Next.js 사용하면서 처음 본 개념이거나, 이해하기 어려운 부분(?)은 붉은색 볼드처리 해봤다.
- beforeFiles
- afterFiles
Preference
Routing: Middleware | Next.js
Learn how to use Middleware to run code before a request is completed.
nextjs.org