[번역] [Next.js] Middleware - Routing

soonrok·2024년 2월 8일

Next.js

목록 보기
1/1
post-thumbnail

해당글은 Next.js 공식문서 중 Middleware - Routing 페이지를 번역한 글입니다.
ChatGPT의 도움을 받았으며 직접 읽고 어색한 내용이나 설명이 다소 난해한 부분을 수정해 가독성을 높혔습니다.

미들웨어는 요청이 완료되기 전에 코드를 실행할 수 있게 합니다. 그런 다음 들어오는 요청을 기반으로 다음과 같은 읽을 수행할 수 있습니다.

  1. request header 또는 response header 수정
  2. response 재작성
  3. redirecting
  4. responding directly (직접 응답)

미들웨어는 캐시된 콘텐츠와 경로가 일치되기 전에 실행됩니다. 자세한 내용은 Matching Paths를 참조하세요.

Convention

프로젝트의 루트 디렉토리에 middleware.ts(또는 .js) 파일을 사용하여 미들웨어를 정의하세요. 예를 들어, pages나 app과 같은 레벨이나, 해당하는 경우 src 내부에 위치시킵니다.

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*',
}

Matching Paths

미들웨어는 프로젝트의 모든 경로에서 호출되며, 다음의 실행 순서를 따릅니다.

  1. next.config.js의 header
  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. Custom matcher config (사용자 정의 mather 사용하기)
  2. Conditional statements (조건문 사용하기)

Matcher

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

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

단일 경로 또는 배열 구문을 사용해 여러 경로를 일치시킬 수 있습니다.

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

matcher 구성은 정규 표현식을 완벽하게 지원하므로, Negative lookahead 기능이나 character matching 검사와 같은 기능가 지원됩니다. 특정 경로를 제외한 모든 경로와 일치하는 Negative lookahead의 예시는 다음과 같습니다.

export const config = {
  matcher: [
    /*
     * 아래의 경로를 제외한 모든 경로에서 미들웨어를 실행한다.
     * 1. api (API routes)
     * 2. _next/static (static files)
     * 3. _next/image (image optimization files)
     * 4. favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}

또한, 미들웨어를 거칠 필요가 없는 prefetches(next/link에서 온 것들)를 missing 배열을 사용해 무시할 수 있습니다

export const config = {
  matcher: [
    /*
     * 아래의 경로를 제외한 모든 경로에서 미들웨어를 실행한다.
     * 1. api (API routes)
     * 2. _next/static (static files)
     * 3. _next/image (image optimization files)
     * 4. 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' },
      ],
    },
  ],
}

💡 Tip: matcher 값은 build-time에 정적으로 분석될 수 있도록 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.

matcher를 구성은 다음과 같습니다.

  1. 무조건 /로 시작해야 합니다.
  2. 이름이 붙혀진 매개변수를 포함할 수 있습니다: /about/:path/about/a/about/b와 일치하지만 /about/a/c와는 일치하지 않습니다.
  3. 이름이 붙혀진 매개변수(:로 시작하는 매개변수)에 대해 수정자를 가질 수 있습니다: /about/:path**이 0개 이상을 의미하기 때문에 /about/a/b/c와 일치합니다. ?는 0개 또는 1개, +는 1개 이상입니다.
  4. 괄호 안에 포함된 정규 표현식을 사용할 수 있습니다: /about/(.*)/about/:path와 동일합니다.

path-to-regexp 문서에서 더 자세한 내용을 확인할 수 있습니다.

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

💡 Tip: 이전 버전과의 호환성을 위해, Next.js는 항상 /public/public/index로 간주합니다. 따라서, /public/:path의 matcher와 일치합니다.

NextResponse

NextResponse API를 이용하면 다음과 같은 일을 할 수 있습니다.

  1. 들어오는 요청을 다른 URL로 redirect
  2. 주어진 URL을 표시함으로써 응답을 rewrite
  3. API 경로, getServerSideProps, rewrite한 목적지(destinations)에 대한 request 헤더를 설정
  4. response 쿠키 설정
  5. response 헤더 설정

미들웨어에서 response을 생성하기 위해서는 다음과 같은 방법이 있습니다.

  1. response를 생성하는 route(페이지라우트 핸들러)를 rewrite 합니다.
  2. NextResponse를 직접 반환합니다. (Producing a Response 참조)

Using Cookies

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

  1. 들어오는 request의 경우, 쿠키는 다음 메소드를 가지고 있습니다: get, getAll, set, delete. 쿠키의 유무를 has로 확인하거나 clear로 모든 쿠키를 제거할 수 있습니다.
  2. 나가는 response의 경우, 쿠키는 다음 메소드들을 가집니다: get, getAll, set, delete.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // 들어오는 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=/` 헤더가 포함될 것입니다.
 
  return response
}

Setting Headers

NextResponse API를 사용하면 request 및 response 헤더를 설정할 수 있습니다. (request 헤더를 설정하는 기능은 Next.js v13.0.0부터 사용 가능합니다.)

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // request 헤더를 복제하고 새로운 헤더인 `x-hello-from-middleware1`를 설정합니다.
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')
 
  // NextResponse.rewrite에서도 request 헤더를 설정할 수 있습니다.
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders,
    },
  })
 
  // 새로운 response 헤더 `x-hello-from-middleware2`를 설정합니다.
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}

💡 Tip: 백엔드 웹 서버 구성에 따라 431 Request Header Fields Too Large 오류가 발생할 수 있으므로 큰 헤더를 설정하는 것을 피하는 것이 좋습니다.

Producing a Response

미들웨어에서 직접 응답하기 위해 Response 인스턴스나 NextResponse 인스턴스를 반환할 수 있습니다. (이 기능은 Next.js v13.0.0부터 사용 가능합니다.)

import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// 경로가 `/api/`로 시작하는 경로에만 미들웨어가 동작하도록 제한합니다.
export const config = {
  matcher: '/api/:function*',
}
 
export function middleware(request: NextRequest) {
  // request를 확인하기 위해 authentication 함수를 호출합니다.
  if (!isAuthenticated(request)) {
    // auth가 인증되지 않은 요청에 대해서 오류 메시지를 나타내는 JSON을 응답합니다.
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

waitUntil and NextFetchEvent

NextFetchEvent 객체는 기본 FetchEvent 객체를 확장하며 waitUntil() 메소드를 포함합니다.

waitUntil() 메소드는 promise를 인수로 받고, promise가 settle될 때까지 미들웨어의 수명을 연장합니다. 이는 백그라운드에서 작업을 수행하는 데 유용합니다.

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에서는 미들웨어에 대한 두 가지 추가 플래그가 도입되었습니다. skipMiddlewareUrlNormalizeskipTrailingSlashRedirect는 고급 사용 사례를 다루기 위한 것입니다.

skipTrailingSlashRedirect는 trailing slashes(맨 끝에 붙는 /)를 추가하거나 제거하는 Next.js 리디렉션을 비활성화합니다. 이를 통해 미들웨어 내에서 특정 경로에 대한 trailing slashes를 유지하면서 다른 경로에 대해서는 유지하지 않도록 사용자 정의 처리를 할 수 있습니다. 이는 점진적인 migration을 더 쉽게 만들 수 있습니다.

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()
  }
 
  // trailing slash 처리 적용
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    req.nextUrl.pathname += '/'
    return NextResponse.redirect(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

미들웨어는 현재 Edge 런타임만 지원합니다. Node.js 런타임은 사용할 수 없습니다.

Version History

VersionChanges
v13.1.0고급 미들웨어 플래그 추가
v13.0.0미들웨어가 request, reponse 헤더를 수정할 수 있고, 응답을 보낼 수 있습니다.
v12.2.0미들웨어가 안정화되었으며, 업그레이드 가이드를 참조하세요.
v12.0.9Edge 런타임에서 절대 URL 강제 적용 (PR)
v12.0.0미들웨어(Meta) 추가
profile
I Will be Relaxed Person

0개의 댓글