Published on

NextJs 13 이후 - Middleware

Authors
  • avatar
    Name
    piano cat
    Twitter

미들웨어를 사용하면 요청이 완료되기전에 코드를 실행시킬 수 있다. 그런다음 들어오는 요청에 따라 요청 또는 응답 헤더를 다시작성, 리디렉션, 수정하거나 직접 응답하여 응답을 수정할 수 있다. 미들웨어는 캐시된 컨텐츠와 경로가 일치하기 전에 실행된다.

Convention

미들웨어를 정의하려면 프로젝트 루트 경로에 middleware.ts 를 사용하여 작성한다.

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

미들웨어는 프로젝트의 모든 경로에 대해 호출된다. 실행순서는 다음와 같다.

  1. headers from next.config.js
  2. redirects from next.config.js
  3. Middleware (rewritesredirects, etc.)
  4. beforeFiles (rewrites) from next.config.js
  5. Filesystem routes (public/_next/static/pages/app/, etc.)
  6. afterFiles (rewrites) from next.config.js
  7. Dynamic Routes (/blog/[slug])
  8. fallback (rewrites) from next.config.js

미들웨어를 실행할수 있는 방법은 두가지로 정의된다.

  1. Custom matcher config
  2. Conditional statements

Matcher

matcher 변수를 설정하여 미들웨어를 실행시키는 경로를 설정할 수 있다.

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

배열을 사용하여 여러 경로를 설정가능하다.

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

또한 matcher 에서는 전체 정규식을 허용하므로 부정 예측 또는 문자 일치와 같은 일치가 지원된다. 특정 경로를 제외한 모든 경로와 일치하는 부정예측의 예는 아래와 같다.

// middleware.ts
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 값은 동적 값이 아닌 상수여야한다. 변수와 같은 동적 값은 무시된다.

matcher 적용 방식

  1. /으로 시작되어야한다.
  2. /about/:path 로 설정하면, /about/a,/about/b는 포함되며, /about/a/c 는 포함되지않는다.
  3. /about/:path* 으로 설정하면 about/a/c 를 포함한다. *는 zero or more, ?는 zero or one, +는 one or more
  4. /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

NextResponse API 는 아래와 같이 쓰인다.

  • 요청 들어온경로에서 다른 URL으로 리다이렉트 한다.
  • 전달된 URL을 재작성하여 응답한다.
  • 응답 쿠키 설정
  • 응답 헤더 설정

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

  1. 응답을 생성하는 경로를 재작성한다. (page, Route Handler)
  2. NextResponse 로 직접 반환한다.

쿠키 사용

쿠키는 일반 헤더이다. 요청안에 저장된 쿠키 헤더가 있다. 응답안에는 set-Cookie 헤더가 있다. NextJs는 NextRequest NextResponse를 사용하여 이러한 쿠키에 접근하고 조작하는 편리한 방법을 제공한다.

  1. 들어온 요청에서 getgetAllset, and delete cookies. 등의 메서드를 사용하여 쿠키를 확인하거나 제거 등을 조작할 수 있다.
  2. 나가는 응답에선  getgetAllset, and delete 등의 메서드를 사용할 수있다.
// 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를 사용하여 요청 그리고 응답헤더를 세팅할 수 있다. (nextJS 13이후 가능)

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

응답 생성

Response 또는 NextResponse 인스턴스를 반환하여 미들웨어에서 직접 응답 할 수도 있다. (NextJs 13 이후 가능)

// middleware.ts
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 })
  }
}

고급 미들웨어 플래그

skipMiddlewareUrlNormalize, skipTrailingSlashRedirect 플래그를 사용하여 고급 미들웨어를 구현할 수 있다.

skipTrailingSlashRedirect는 후행 슬래시를 추가하거나 제거하기 위해 nextJS 기본 리다이렉션을 비활성화 할수 있으므로, 미들웨어 내에서 사용자 지정처리가 가능해 일부 경로에 대해서는 후행 슬래시를 유지할 수 있지만 다른 경로에서는 유지 관리가 불가능하므로 증분 마이그레이션이 더 쉬워진다.

// next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
// middleware.ts
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)
  }
}

skipMiddlewareUrlNormalizeNext.js가 수행하는 URL 정규화를 비활성화하여 직접 방문과 클라이언트 전환을 동일하게 처리할 수 있다. 잠금 해제되는 원래 URL을 사용하여 완전한 제어가 필요한 몇 가지 고급 사례가 있다.

// next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}
// middleware.ts
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
}

참고