본문 바로가기
개인공부

Next.js Docs (12) Middleware

by 강물둘기 2023. 7. 11.

Middleware

미들웨어를 사용하면 response가 완료되기 전에 코드를 실행할 수 있다. 그런 다음 요청을 받아서 response를 다시 작성, 리디렉션, request header나 response header 수정, 또는 직접 응답할 수 있다.

 

캐시된 컨텐츠와 route가 match되기 전에 미들웨어가 실행된다. 자세한 내용은 Matching Paths 참고.

 

 

Convention

프로젝트의 root에 있는 middleware.js 파일을 사용해서 미들웨어를 정의한다. 예를 들어 페이지또는 app과 동일한 레벨이거나, src 내부에 정의한다.

 

 

Example

// 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

프로젝트의 모든 route에서 미들웨어가 호출된다. 다음은 실행순서이다 :

 

1. next.config.js 파일의 headers

2. next.config.js 파일의 redirects

3. 미들웨어(rewrites, redirects 등)

4. next.config.js 파일의 beforeFiles(rewrites)

5. 파일시스템 routes( public/ , _next/static/ , pages/ , app/ 등)

6. next.config.js 파일의 afterFiles(rewrites)

7. 동적 routes ( /blog/[slug] )

8. next.config.js 파일의 fallback(rewrites)

 

미들웨어가 실행될 경로를 정의하는 방법은 두 가지가 있고, 다음과 같다 :

 

1. Custom matcher config

2. Conditional statements

 

 

Matcher

matcher를 사용하면 미들웨어를 필터링하여 특정 경로에서 실행할 수 있다.

// middleware.js

export const config = {
  matcher: '/about/:path*',
}

 

단일 경로 또는 여러 경로를 array syntax를 사용하여 match 시킬 수 있다.

// middleware.js

export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

 

matcher config는 정규표현식을 허용하므로 negative lookaheads나 문자 일치 같은 matching을 지원한다. 특정 경로를 제외한 모든 경로와 일치하는 negative lookahead 예제는 다음과 같다.

// middleware.js

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 값은 빌드 시 정적으로 분석할 수 있도록 하기위해 상수여야 한다. 변수와 같은 동적값은 무시된다.

 

Configured matchers :

1. 반드시 / 로 시작해야 한다.

 

2. named 파라미터를 포함할 수 있다 : /about/:path 는 /about/a , /about/b를 포함하지만 /about/a/c는 포함할 수 없다.

 

3. named 파라미터( :로 시작하는)는 수식어를 포함할 수 있다.  : * 은 0혹은 그이상이기 때문에  /about/:path* 은 /about/a/b/c를 포함한다.  ? 는 0 또는 1 및 + 1 이상을 의미한다.

 

4. 괄호 안에 포함된 정규표현식을 사용할 수 있다 : /about/(.*) 은 /about/:path* 와 동일하다.

 

자세한 사항은 path-to-regexp 문서 참조.

 

* 알아두면 좋은 것 : 이전 버전과 호환성을 위해 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))
  }
}

 

 

NextResponse

NextResponse API를 사용하면 다음과 같은 작업을 할 수 있다 :

- 수신 request를 다른 URL로 redirect

- 주어진 URL을 표시하여 response rewrite

- API Routes, getServerSideProps 및 rewrite에 대한 request 헤더 설정

- response 쿠키 설정

- response 헤더 설정

 

미들웨어에서 response를 생성하려면 다음과 같이 작업한다:

1. response를 생성하는 route(페이지 또는 Route Handler)에 rewrite

2. NextResponse 직접 반환. (밑의 Producing a Response 참조)

 

 

Using Cookies

Cookie는 일반 헤더이다. request 시 쿠키 헤더에 저장된다. response에서는 Set-Cookie 헤더에 있다. Next.js는 NextRequest 및 NextResponse의 쿠키 확장을 통해 이러한 쿠키에 접근하고 조작할 수 있는 편리한 방법을 제공한다.

 

1. 들어오는 request의 경우 쿠키는 다음과 같은 method를 제공한다 : get, getAll, set, delete

    has로 쿠키의 존재를 확인하거나 clear로 모든 쿠키를 제거할 수도 있다.

2. response를 내보내는 경우 쿠키에는 get, getAll, set, delete method가 있다.

// 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
}

 

 

Setting Headers

NextResponse API를 사용하여 response 및 request 헤더를 설정할 수 있다.( request 헤더 설정은 Next.js 13.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
}

* 알아두면 좋은것 : 백엔드 웹 서버 구성에 따라 431 Request Header Fields Too Large 에러를 발생시킬 수 있으므로 큰 헤더를 설정하는것을 지양해야한다.

 

 

Producing a Response

Response 또는 NextResponse 인스턴스를 반환하여 미들웨어에서 직접 응답할 수 있다.(Next.js 13.1.0버전 이후로 가능)

// middleware.ts

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

13.1 버전의 Next.js에서 고급 사용 case를 처리하기 위해 skipMiddlewareUrlNormalize 와 skipTrailingSlashRedirect라는 두 개의 플래그가 미들웨어용으로 추가되었다.

 

skipTrailingSlashRedirect를 사용하면 Next.js 기본 redirect를 비활성화하여 따라오는 슬래시를 추가하거나 제거할 수 있다. 따라서 미들웨어 내부에서 custom 처리를 수행할 수 있게 되어 일부 경로는 슬래시를 유지하고, 다른 경로에서는 그렇지 않게 함으로써 더 쉽게 증분(incremental) 마이그레이션을 할 수 있다.

// 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 normalizing을 비활성화 할 수 있다. 이러한 잠금이 해제되는 원래의 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
}

 

Version History

Version Changes
v13.1.0 향상된 미들웨어 flags 도입
v13.0.0 미들웨어가 request 헤더, response 헤더 수정가능, response 전송가능
v12.2.0 미들웨어 안정화, upgrade guide 참조
v12.1.0 Edge 런타임에서 절대 URL 적용
v12.0.0 미들웨어(beta) 도입

 

Reference

- https://nextjs.org/docs/app/building-your-application/routing/middleware#producing-a-response

 

Routing: Middleware | Next.js

Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly. Middleware runs before cached co

nextjs.org

 

댓글