[Next.js 14] 1. Routing - 12

2025. 2. 18. 16:15·Skill/Next.js
반응형

13. Middleware

  • Middleware는 요청이 완료되기 전에 코드를 실행시킬 수 있는 환경을 제공한다.
  • 사용자 요청을 기반으로 응답을 재작성하거나, request or response header를 수정하거나 바로 응답하는 등의 작업이 가능하다.
  • Caching Content와 Route가 발생하기 전에 실행된다.

13-1. Use Cases

  • 사용하면 좋은 경우.
    1. 인증 및 인가 : Route handler or Page에 접근하기 전에 session cookies를 확인하여 사용자 신원에 대한 인증 및 인가.
    2. Server Side Redirect : Server Level에서 조건에 따라 사용자를 Redirect.
    3. Path 재생성 : A/B Test, 신규 기능 적용, legacy path 변경 등 요청 정보에 따라 기존 Path 정보를 재생성.
    4. Bot 검출 : bot traffic을 막고 검출.
    5. 로깅 및 분석 : 요청 데이터를 캡쳐해서 로그를 남기거나 분석할 수 있다.
    6. 기능 on/off : 신규 기능이나 테스트 시 특정 기능의 활성, 비활성화 지정.
  • 사용할 때 주의해야 하는 경우.
    1. 복잡한 데이터 Fetch와 조작 : Middleware는 해당 목적으로 설계되지 않았으므로 Route Handler or Server utilities을 사용.
    2. 복잡한 계산 로직 : Middleware는 가볍고 빠르게 응답하지 않으면 page load에 딜레이가 생긴다. Route Handler 추천.
    3. 광범위한 세션 관리 : Middleware는 기본적인 세션 관리가 가능하지만, 해당 목적으로는 별도의 서비스를 사용하거나 Route Handler 내에서 이루어져야 한다.
    4. 직접적인 DB 조작 : Middleware에서 DB 조작은 추천되지 않는다. Route Handler나 Server utilities를 사용.
  • middleware.ts | js 파일을 Project root 경로에 정의할 수 있으며, 관련 로직을 별도로 분리하여 import해서 사용해야 충돌을 피하고 성능을 유지할 수 있다.

13-2. Example

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

 

13-3. Matching Paths

  • Project 내 모든 Route에 대해 Middleware가 실행 되기 때문에, 원하는 경로를 타겟팅하고 제외하는 것이 중요.
  • 실행 순서는 다음과 같다.
    1. next.config.js의 headers
    2. next.config.js의 redirects
    3. Middleware
    4. next.config.js의 beforeFiles
    5. File System Route
    6. next.config.js의 afterFiles
    7. Dynamic Route
    8. next.config.js의 fallback(rewrites)
  • Middleware에서 실행될 경로를 정의하는 2가지 방법.

1. Matcher

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


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


export const config = {             // 정규표현식 Matcher
  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).*)',
  ],
}


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)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
 
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      has: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
 
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      has: [{ type: 'header', key: 'x-present' }],
      missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
    },
  ],
}
  • Matcher는 Middleware에서 특정 경로를 필터링할 수 있게 해준다.
  • 고급 정규표현식을 포함해서 Matcher를 설정할 수도 있다.
  • matcher 정보는 Build time에 분석되므로 동적으로 작성하면 안된다.
  • missing, has 배열 정보로 특정 조건을 걸 수도 있다.
    - missing: 특정 key가 없으면 Middleware를 실행한다.
    - has: 특정 key가 있으면 Middleware를 실행한다.
1. matcher는 / 로 시작해야 한다.
2. /about/:path -> /about/a, /about/b 등 매칭, /about/v/c는 매칭 X.
3. /about/:path* -> /about/a, /about/b, /about/v/c 등 매칭.
4. /about/(.*) == /about/:path*

 

2. 조건문 활용

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

 

13-4. NextResponse

  • NextResponse가 제공하는 주요 기능
    1. 요청과 다른 URL로 redirect.
    2. 주어진 URL로 응답하는 rewrite.
    3. Route Handler, getServerSideProps, rewrite 목적지를 위한 request header 정보 추가.
    4. 응답 쿠키 추가.
    5. 응답 헤더 추가.
  • NextResponse에서 보틍 응답을 생성하기 위해선,
    1. page or Route Handler로의 rewrite 응답.
    2. NextResponse를 직접 생성하여 응답.

13-5. Cookies 사용

  • Cookies는 일반 헤더로, Request 객체에 저장되어 있으며 Response에 Cookie를 Set할 수 있다.
  • NextRequest와 NextResponse의 cookies로 손쉽게 Cookies에 대한 조작이 가능하게 한다.
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=/` header.
 
  return response
}
  1. 요청 Request : get, getAll, delete, has, clear 등의 함수 제공.
  2. 응답 Response : get, getAll, set, delete 등의 함수 제공.

13-6. Setting Headers

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
}
  • NextResponse API를 이용해서 요청 및 응답 객체에 header 정보를 추가할 수 있다.
import { NextRequest, NextResponse } from 'next/server'
 
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
 
const corsOptions = {
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
 
export function middleware(request: NextRequest) {
  // Check the origin from the request
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)
 
  // Handle preflighted requests
  const isPreflight = request.method === 'OPTIONS'
 
  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }
 
  // Handle simple requests
  const response = NextResponse.next()
 
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin)
  }
 
  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value)
  })
 
  return response
}
 
export const config = {
  matcher: '/api/:path*',
}
  • CORS의 Simple Request, Preflighted Request 관련 header 정보 설정도 가능하다.

13-7. Producing a Response

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 }
    )
  }
}
  • Middleware에서 Response or NextResponse 객체를 직접 반환할 수 있다.
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()
}
  • NextFetchEvent는 기존 FetchEvent를 확장하여 waitUntil() 함수가 포함된 객체로, middleware의 인자로 전달된다.
  • waitUntil() : 인자로 Promise 객체를 받고, Middleware를 백 그라운드에서 Promise가 처리될 때 까지 실행시켜둘 수 있다.

 

처음 뵙겠습니다.

Next.js 사용하면서 처음 본 개념이거나, 이해하기 어려운 부분(?)은 붉은색 볼드처리 해봤다.

  • beforeFiles
  • afterFiles

 

Preference

  • https://nextjs.org/docs/14/app/building-your-application/routing/middleware
 

Routing: Middleware | Next.js

Learn how to use Middleware to run code before a request is completed.

nextjs.org

저작자표시 (새창열림)
'Skill/Next.js' 카테고리의 다른 글
  • [Next.js 14] 2. Data Fetching - 1
  • [Next.js 14] 1. Routing - 13
  • [Next.js 14] 1. Routing - 11
  • [Next.js 14] 1. Routing - 외전
뜸부깅
뜸부깅
코딩에 대한 여러 개인적인 생각을 정리하고 공부를 하는 공간입니다!!
  • 뜸부깅
    코오오딩
    뜸부깅
  • 전체
    오늘
    어제
    • Note (429)
      • Skill (31)
        • Java & Spring (9)
        • Javascript & HTML & CSS (0)
        • React (0)
        • Next.js (22)
      • CodingTest (389)
        • 백준 온라인 저지(BOJ) (140)
        • 프로그래머스(Programmers) (79)
        • LeetCode (170)
      • Algorithm & Data Structure (6)
      • [Project] 포트폴리오 (3)
        • Front end (3)
        • Back end (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    medium
    백준7576자바
    백준7576
    BOJ
    leetcode 2236
    알고리즘
    프로그래머스
    백준2751
    자바
    component-scan
    TypeScript
    boj2108
    Easy
    boj1427
    백준
    백준1427
    Java
    next 14
    백준1260
    meidum
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
뜸부깅
[Next.js 14] 1. Routing - 12
상단으로

티스토리툴바