ReactNextCentral

미들웨어

Published on
요청이 완료되기 전에 코드를 실행하는 미들웨어의 사용법을 학습하세요.
Table of Contents

미들웨어는 요청이 완료되기 전에 코드를 실행할 수 있게 해줍니다. 들어오는 요청을 기반으로 응답을 리라이트, 리디렉션, 요청 또는 응답 헤더 수정, 또는 직접 응답하는 방식으로 수정할 수 있습니다.

미들웨어는 캐시된 컨텐츠와 라우트가 일치되기 전에 실행됩니다. 경로 매칭에서 자세한 내용을 확인하세요.


규칙

프로젝트의 루트에 middleware.ts (또는 .js) 파일을 사용하여 미들웨어를 정의하세요. 예를 들면, pages 또는 app와 동일한 수준에서 또는 해당하는 경우 src 내부에 있습니다.


예제

middleware.ts
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*',
}
JavaScript
middleware.js
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*',
}

경로 매칭

미들웨어는 프로젝트의 모든 라우트에 대해 호출됩니다. 다음은 실행 순서입니다.

  1. next.config.js에서의 headers
  2. next.config.js에서의 redirects
  3. 미들웨어 (rewrites, redirects 등)
  4. next.config.js에서의 beforeFiles (rewrites)
  5. 파일시스템 라우트 (public/, _next/static/, pages/, app/ 등)
  6. next.config.js에서의 afterFiles (rewrites)
  7. 동적 라우트 (/blog/[slug])
  8. next.config.js에서의 fallback (rewrites)

미들웨어가 실행될 경로를 정의하는 두 가지 방법이 있습니다.

  1. 사용자 정의 매처 설정
  2. 조건문

매처

matcher는 특정 경로에서 미들웨어를 실행하도록 필터링 할 수 있게 해줍니다.

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

배열 문법을 사용하여 단일 경로 또는 여러 경로와 일치시킬 수 있습니다.

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

matcher 설정은 완전한 정규 표현식을 허용하므로 부정 전방 탐색이나 문자 매칭과 같은 매칭이 지원됩니다. 특정 경로를 제외한 모든 것과 일치하는 부정 전방 탐색의 예는 다음과 같습니다.

middleware.js
export const config = {
  matcher: [
    /*
     * 다음으로 시작하는 경로를 제외한 모든 요청 경로와 일치:
     * - api (API 라우트)
     * - _next/static (정적 파일)
     * - _next/image (이미지 최적화 파일)
     * - favicon.ico (파비콘 파일)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}

matcher 값은 빌드 시간에 정적으로 분석될 수 있도록 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.

설정된 매처는 아래 규칙을 따라야 합니다.

  1. /로 시작해야 합니다.
  2. 명명된 매개 변수를 포함할 수 있습니다. /about/:path/about/a/about/b와 일치하지만 /about/a/c와는 일치하지 않습니다.
  3. 명명된 매개 변수 (':'로 시작하는)에 수정자를 가질 수 있습니다. /about/:path**제로 또는 더 많은 때문에 /about/a/b/c와 일치합니다. ?제로 또는 하나 이고 +하나 또는 더 많은 입니다.
  4. 괄호로 묶인 정규 표현식을 사용할 수 있습니다. /about/(.*)/about/:path*와 동일합니다.

path-to-regexp 문서에서 더 자세한 내용을 읽어보세요.

하위 호환성을 위해 Next.js는 항상 /public/public/index로 간주합니다. 따라서 /public/:path 매처는 일치하게 됩니다.

조건문

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))
  }
}
JavaScript
middleware.js
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 라우트, getServerSidePropsrewrite 대상에 대한 요청 헤더 설정
  • 응답 쿠키 설정
  • 응답 헤더 설정
App Router Only

미들웨어에서 응답을 생성하려면 다음과 같은 방법이 있습니다.

  1. 응답을 생성하는 라우트(Page 또는 Route Handler)로 rewrite합니다.
  2. NextResponse를 직접 반환합니다. 응답 생성을 참조하세요.
Page Router Only

미들웨어에서 응답을 생성하려면 다음과 같은 방법이 있습니다.

  1. 응답을 생성하는 라우트(Page 또는 Edge API Route)로 rewrite합니다.
  2. NextResponse를 직접 반환합니다. 응답 생성을 참조하세요.

쿠키 사용하기

쿠키는 일반적인 헤더입니다. Request에서는 Cookie 헤더에 저장되며 Response에서는 Set-Cookie 헤더에 있습니다. Next.js는 NextRequestNextResponse에 있는 cookies 확장을 통해 이러한 쿠키에 접근하고 조작하는 편리한 방법을 제공합니다.

  1. 들어오는 요청에 대해 cookies는 다음 메소드와 함께 제공됩니다. 쿠키 get, getAll, set, delete. has로 쿠키의 존재 여부를 확인하거나 clear로 모든 쿠키를 삭제할 수 있습니다.
  2. 나가는 응답에 대해서 cookies는 다음 메소드를 가집니다. get, getAll, set, delete
middleware.ts
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=/test` 헤더가 있을 것입니다.

  return response
}
JavaScript
middleware.js
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
}

헤더 설정하기

NextResponse API를 사용하여 요청 및 응답 헤더를 설정할 수 있습니다 (요청 헤더 설정은 Next.js v13.0.0부터 사용 가능합니다).

middleware.ts
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.rewrite에서도 요청 헤더를 설정할 수 있습니다.
  const response = NextResponse.next({
    request: {
      // 새로운 요청 헤더
      headers: requestHeaders,
    },
  })

  // 새 응답 헤더 `x-hello-from-middleware2`를 설정합니다.
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}
JavaScript
middleware.js
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.rewrite에서도 요청 헤더를 설정할 수 있습니다.
  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 오류를 발생시킬 수 있으므로 주의해야 합니다.


응답 생성하기

미들웨어에서 직접 Response 또는 NextResponse 인스턴스를 반환하여 응답할 수 있습니다. (Next.js v13.1.0부터 사용 가능합니다.)

middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// 미들웨어를 `/api/`로 시작하는 경로로 제한합니다.
export const config = {
  matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
  // 요청을 확인하기 위해 인증 함수를 호출합니다.
  if (!isAuthenticated(request)) {
    // 오류 메시지를 나타내는 JSON으로 응답합니다.
    return new NextResponse(
      JSON.stringify(
        { success: false, message: 'authentication failed' }
    ),
      { status: 401, headers: 
        { 'content-type': 'application/json' } 
      }
    )
  }
}
JavaScript
middleware.js
import { NextResponse } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// 미들웨어를 `/api/`로 시작하는 경로로 제한합니다.
export const config = {
  matcher: '/api/:function*',
}

export function middleware(request) {
  // 요청을 확인하기 위해 인증 함수를 호출합니다.
  if (!isAuthenticated(request)) {
    // 오류 메시지를 나타내는 JSON으로 응답합니다.
    return new NextResponse(
      JSON.stringify(
        { success: false, message: 'authentication failed' }
    ),
      { status: 401, headers: 
        { 'content-type': 'application/json' } 
      }
    )
  }
}

고급 미들웨어 플래그

Next.js의 v13.1에서는 고급 사용 사례를 처리하기 위해 두 가지 추가 플래그인 skipMiddlewareUrlNormalizeskipTrailingSlashRedirect가 도입되었습니다.

skipTrailingSlashRedirect는 트레일링 슬래시(trailing slash)를 추가하거나 제거하기 위한 Next.js 기본 리다이렉트를 비활성화하여 미들웨어 내에서 커스텀 처리를 할 수 있습니다. 이로 인해 일부 경로에는 트레일링 슬래시를 유지하고 다른 경로에는 트레일링 슬래시를 사용하지 않게 할 수 있어 점진적인 마이그레이션이 더욱 쉬워집니다.

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()
  }

  // 트레일링 슬래시 처리 적용
  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.js
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로 정규화됩니다.
}