본문 바로가기

넥스트

[next.js] 10. 미들웨어

반응형
이 내용은  next.js 13버전의 공식문서를 한글로 번역한 내용을 토대로 정리되었습니다. 최신 정보와 정확한 내용은 공식 사이트의 문서를 참고해주세요. https://nextjs.org/docs/app/building-your-application/routing/middleware

 

  미들웨어

미들웨어는 요청이 완료되기 전에 코드를 실행하여 응답을 수정할 수 있습니다. 요청에 따라 리디렉션, 재작성, 요청 또는 응답 헤더 수정 또는 직접 응답을 생성할 수 있습니다.

 

미들웨어는 캐시된 콘텐츠와 라우팅이 일치하기 전에 실행됩니다. 매칭 경로에 대한 자세한 내용은 

https://nextjs.org/docs/app/building-your-application/routing/middleware#matching-paths (매칭 경로)

를 참조하세요.

 

프로젝트 루트에 있는 middleware.ts(또는 .js) 파일을 사용하여 미들웨어를 정의합니다. 예를 들어, 페이지나 앱과 같은 수준에서 또는 해당하는 경우 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))
}

// 매칭 경로에 대해 자세히 알아보려면 아래를 참조하세요.
export const config = {
  matcher: '/about/:path*',
}

  매칭 경로

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

 

  • next.config.js의 헤더
  • next.config.js의 리디렉션
  • 미들웨어 (리디렉션, 재작성 등)
  • beforeFiles (리디렉션) from next.config.js
  • Filesystem routes (public/, _next/static/, pages/, app/, 등)
  • afterFiles (리디렉션) from next.config.js
  • 동적 경로 (/blog/[slug])
  • fallback (리디렉션) from next.config.js

경로를 미들웨어에서 실행할 두 가지 방법이 있습니다.

  • 사용자 정의 매처 config
  • 조건문

  매처

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

// middleware.js

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

 

여러 경로를 일치시킬 수 있으며 배열 구문을 사용할 수 있습니다

middleware.js

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

 

matcher config 전체 정규식을 허용하므로 부정적인 lookahead 또는 문자 일치와 같은 매칭이 지원됩니다. 특정 경로를 제외한 모든 경로를 일치시키는 부정적인 lookahead의 예는 다음과 같습니다.

// middleware.js

export const config = {
  matcher: [
    /*
     * 모든 요청 경로를 제외하고 다음으로 시작하는 경로를 일치시킵니다.
     * - api (API 루트)
     * - _next/static (정적 파일)
     * - _next/image (이미지 최적화 파일)
     * - favicon.ico (파비콘 파일)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}
팁) matcher 값은 빌드 시 정적으로 분석할 수 있도록 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.

구성된 매처(Configured matchers):

  • / 로 시작해야 합니다.
  • named parameters를 포함할 수 있습니다:  /about/:path/about/a/about/b를 일치시키지만 /about/a/c는 일치하지 않습니다.
  • named parameters에 대한 수정자를 사용할 수 있습니다 (:로 시작): /about/:path*/about/a/b/c를 일치시키기 때문에 * 는 0개 이상, ? 는 0개 또는 1개, + 는 1개 이상입니다.
  • 정규 표현식으로 괄호로 묶을 수 있습니다: /about/(.)는 /about/:path와 동일합니다.

  조건문

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 API

NextResponse API는 다음을 수행 할 수 있습니다:

  • 수신 요청을 다른 URL로 리디렉션합니다.
  • 지정된 URL을 표시하여 응답을 다시 작성합니다.
  • API 경로(API Routes), getServerSideProps 및 재작성 목적지용 요청 헤더를 설정합니다.
  • 응답 쿠키를 설정합니다.
  • 응답 헤더를 설정합니다.

 

미들웨어에서 응답을 생성하려면 다음을 수행할 수 있습니다:


  쿠키 사용

쿠키는 일반 헤더입니다. 요청 시 쿠키 헤더(Cookie header)에 저장됩니다. 응답 시 쿠키 설정 헤더(Set-Cookie header)에 저장됩니다. Next.js는 NextRequest 및 NextResponse의 쿠키 확장을 통해 이러한 쿠키에 액세스하고 조작할 수 있는 편리한 방법을 제공합니다.

(개인 정리) 쿠키는 일반 헤더로서 요청 시에는 Cookie 헤더에 저장 되고, 응답 시 에는 Set-Cookie 헤더에 저장된다. next.js 에서는 쿠키 기능에 대한 확장된 기능을 제공하는 NextRequest 와 NextResponse 를 제공한다.


- 수신 요청의 경우 쿠키에는 쿠키 가져오기, 모두 가져오기, 설정 및 삭제(get, getAll, set)와 같은 방법이 함께 제공됩니다. 쿠키가 있는지 확인하거나 삭제된 쿠키를 모두 제거할 수 있습니다.


- 발신 응답의 경우 쿠키는 다음과 같은 get, getAll, 설정(set) 및 삭제 방법을 사용합니다.

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
}

  헤더 설정

NextResponse API를 사용하여 요청(request) 및 응답(response) 헤더를 설정할 수 있습니다 (요청 헤더 설정은 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
}
알아두면 좋은점: 백엔드 웹 서버 구성에 따라 431개의 요청 헤더 필드가 너무 클 수 있으므로 큰 헤더를 설정하지 마십시오.

  응답 생성

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

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

  고급 미들웨어 플래그

Next.js의 v13.1에서는 고급 사용 사례를 처리하기 위해 MiddlewareUrlNormalize 및 SkipTrailingSlashRedirect라는 두 개의 추가 플래그가 미들웨어용으로 도입되었습니다.

 

skipTrailingSlashRedirect

는 Next.js의 기본 리디렉션을 비활성화하여 trailing slash(경로 끝에 슬래시)를 추가하거나 제거할 수 있도록 하고, 미들웨어 내에서 사용자 지정 처리를 허용하여 일부 경로의 trailing slash를 유지하고 다른 경로에서는 유지하지 않도록 하여 점진적인 마이그레이션을 용이하게 합니다.

(추가 - 공식문서에 없음)
여기서 trailing slash는 경로 끝에 있는 슬래시를 의미합니다. 예를 들어, /about와 /about/는 동일한 경로이지만, /about에는 trailing slash가 없고 /about/에는 trailing slash가 있습니다.

skipTrailingSlashRedirect는 Next.js가 기본적으로 trailing slash를 추가하거나 제거하는 리디렉션을 수행하는 것을 방지합니다. 이 옵션을 사용하면 trailing slash를 유지하거나 제거하는 방법을 미들웨어에서 처리할 수 있습니다.

이 옵션은 점진적인 마이그레이션을 용이하게 합니다. 예를 들어, 웹 사이트에서 trailing slash를 사용하지 않기로 결정한 경우, skipTrailingSlashRedirect를 사용하여 점진적으로 trailing slash를 제거할 수 있습니다.

먼저 skipTrailingSlashRedirect를 true로 설정하고, 모든 경로에 대해 trailing slash를 제거하는 미들웨어를 작성합니다. 그런 다음, 이전에 trailing slash를 사용했던 경로에 대한 링크를 업데이트합니다. 마지막으로, skipTrailingSlashRedirect를 false로 설정하여 Next.js가 다시 trailing slash를 추가하는 리디렉션을 수행하도록 합니다.


이 옵션을 사용하면 더 많은 유연성을 얻을 수 있습니다. 예를 들어, 일부 경로의 trailing slash를 유지하고 다른 경로에서는 유지하지 않도록 할 수 있습니다.

 

이 옵션을 사용하려면 next.config.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()
  }
 
  // apply trailing slash handling
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    req.nextUrl.pathname += '/'
    return NextResponse.redirect(req.nextUrl)
  }
}

 

skipMiddlewareUrlNormalize

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)
  // with the flag this now /_next/data/build-id/hello.json
  // 플래그가 없으면 /hello로 정규화됩니다
}

 

반응형