본문 바로가기

Next.js

Next.js 11-11 Routing middleware

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

 

  • Convention

  프로젝트 루트에 있는 middleware.ts(또는 .js) 파일을 사용하여 미들웨어를 정의한다. 예를 들어 페이지 또는 앱과 동일한 수준 또는 해당하는 경우 src 내부에 있다.

 

  • Example
// 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.redirecct(new URL('/home', request.url));
}

// See 'Matching Paths' bellow th learn more
export const config = {
  matcher: '/about/:path*',
};

 

  • Matching Paths

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

  1. next.config.js의 헤더
  2. next.config.js에서 리디렉션
  3. 미들웨어(재작성, 리디렉션 등)
  4. next.config.js의 beforeFiles(재작성)
  5. 파일 시스템 경로(pulic/, _next/static/, pages/, app/ 등)
  6. next.config.js에서 afterFiles(재작성)
  7. 동적 경로(/blog/[slug])
  8. next.config.js에서 폴백(rewrites)

  미들웨어가 실행될 경로를 정의하는 두 가지 방법이 있다. 첫 번째는 Custom matcher config이고, 두 번째는 Conditional statements이다.

 

  • Matcher

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

// middleware.js

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

  배열 구문을 사용하여 단일 경로 또는 다중 경로를 일치시킬 수도 있다.

// middleware.js

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

  matcher 구성은 전체 정규식을 허용하므로 부정 예측 또는 문자 일치와 같은 matching이 지원된다. 특정 경로를 제외한 모든 경로와 일치하는 부정적인 미리 보기의 예는 다음에서 볼 수 있다.

// middleware.js

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 값은 빌드 시 정적으로 분석될 수 있도록 상수여야 한다. 변수와 같은 동적 값은 무시된다.

  1. /로 시작해야 한다.
  2. 명명된 매개변수를 포함할 수 있음: /about/path는 /about/a 및 /about/b와 일치하지만 /about/c와 일치하지 않음.
  3. 명명된 매개변수에 수정자를 가질 수 있다. (:)로 시작한다. /about/:path*는 *가 0 이상이므로 /about/a/b/c와 일치한다. ?은 0 또는 1이고 +는 1 이상이다.
  4. 괄호로 묶인 정규식을 사용할 수 있다. /about/(.*)는 /about/:path*와 동일하다.

 

  • Conditional Statements
// 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('/dashbaord/user', request.url));
  }
}

 

  • NextResponse

  NextResponse API를 사용하면 다음을 수행할 수 있다.

- 들어오는 요청을 다른 URL로 리디렉션

- 주어진 URL을 표시하여 응답을 다시 작성

- API 경로, getServerSideProps 및 재작성 대상에 대한 요청 헤더 설정

- 응답 쿠키 설정

- 응답 헤더 설정

  미들웨어에서 응답을 생성하려면 다음을 수행할 수 있다. 먼저, 응답을 생성하는 경로(페이지 또는 Edge API 경로)에 다시 쓰기가 가능하다. 그리고 NextResponse를 직접 반환할 수 있다.

 

  • Using Cookies

  쿠키는 일반 헤더이다. 요청 시 쿠키 헤더에 저장된다. 응답에서 쿠키는 Set-Cookie 헤더에 있다. Next.js는 NextRequest 및 NextResponse의 쿠키 확장을 통해 이러한 쿠키에 액세스 하고 조작하는 편리한 방법을 제공한다.

  1. 들어오는 요청의 경우 쿠키는 get, getAll, set 및 delete와 같은 메서드와 함께 제공된다. has를 사용하여 쿠키의 존재를 확인하거나 clear를 사용하여 모든 쿠키를 제거할 수 있다.
  2. 나가는 응답의 경우 쿠키에는 get, getAll, set 및 delete 메서드가 있다.
// middleware.js

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 incomming request
  // Getting cookies from the request using the 'requestCookies' API
  let cookie = request.cookies.get('nextjs')?.value;
  console.log(cookie); // => 'fast'
  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: '/test',
  })
  cookie = response.cookies.get('vercel');
  console.log(cookie); // => { name: 'vercel', value: 'fast', path: '/test' }
  // The outgoing response will have a 'Set-Cookie:vercel=fast;path=/test' header.
  
  return response;
}

 

  • Setting Headers

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

// middleware.ts

import ( NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(reqeust: NextRequest) {
  // Clone the request headers and set a new header 'x-hello-from-middleware1'
  const reqeustHeaders = 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;
}

 

  • Producing a Response

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

// middleware.ts

import { NextReqeust, NextResponse } 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 reqeust
  if (!isAuthenticated(reqeust)) {
    // Response with JSON indicating an error message
    return new NextResponse(
      JSON.stringify({ success: false, message: 'authentication failed' }),
      { status: 401, headers: { 'content-type': 'application/json' } },
    );
  }
}

 

  • Advanced Middleware Flags

  Next.js v13.1에서는 고급 사용 사례를 처리하기 위해 미들웨어용 skipMiddlewareUrlNormalize 및 skipTrailingSlashRedirect라는 두 개의 추가 슬래시가 도입되었다.

  skipTrailingSlashRedirect는 후행 슬래시를 추가하거나 제거하기 위한 Next.js 기본 리디렉션을 비활성화할 수 있다. 미들웨어 내에서 사용자 지정 처리를 허용하여 일부 경로에 대해 후행 슬래시를 유지할 수 있지만 다른 경로는 유지하지 않고 더 쉽게 증분 마이그레이션을 허용할 수 있다.

// next.config.js

module.exports = {
  skipTrailingSlashRedirect: true,
};
// middleware.js

const lagacyPrefixes = ['/docs', '/blog'];

export default async function middleware(req) {
  const { pathname } = requ.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.redurect(req.nextUrl);
  }
}

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

// next.config.js

module.exports = {
  skipMiddlewareUrlNormailize: true,
};
// middleware.js

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
}