[ Routing ] Middleware

차차·2023년 5월 17일
0

Next Docs

목록 보기
13/34
post-thumbnail

Middleware

미들웨어를 사용하면 요청이 완료되기 전에 코드를 실행할 수 있다. 그런 다음 들어오는 요청에 따라 request 또는 response header를 rewriting, redirecting , modifying하거나 직접 응답하여 response를 수정할 수 있다.


미들웨어 파일 생성

프로젝트의 루트에 있는 파일 middleware.ts(또는 )을 사용하여 미들웨어를 정의한다. 예를 들어, pageapp과 같은 레벨에 파일을 생성한다. 만약 src가 존재할 경우 src 파일 안에 생성한다.


Middleware 사용 예시

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


매칭 경로

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

  1. next.config.js 에서 headers
  2. next.config.js 에서 redirects 
  3. Middleware (rewritesredirects 등)
  4. next.config.js 에서 beforeFiles (rewrites)
  5. Filesystem 경로 (public/_next/static/pages/app/ 등)
  6.  next.config.js 에서 afterFiles (rewrites)
  7. 동적 경로 (/blog/[slug])
  8. next.config.js 에서 fallback (rewrites)

미들웨어가 실행될 경로를 정의하는 방법은 두 가지가 있다.

1. Matcher

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

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

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

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

matcher 구성에서는 negative lookahead(?!)나 문자 매칭과 같은 매칭이 지원되는 전체 정규식을 사용할 수 있다. 특정 경로를 제외한 모든 경로를 매칭하는 negative lookahead의 예는 다음과 같다.

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


💡  negative lookahead ( ?! )

특정 조건을 만족하는 문자열을 찾되, 그중에서 제외를 하고싶은 경우 사용한다.


Matcher 규칙

  • 반드시 /로 시작한다.
  • 이름 있는 매개변수를 포함할 시킬 수 있다. /about/:path/about/a/about/b와 일치하지만 /about/a/c와는 일치하지 않는다.
  • 이름 있는 매개변수에 modifiers(:로 시작하는)를 사용할 수 있다. /about/:path**가 0개 이상이기 때문에 /about/a/b/c와 일치한다. ?는 0개 또는 1개, +는 1개 이상이다.
  • 괄호로 묶인 정규식을 사용할 수 있다. /about/(.*)/about/:path*와 동일하다.
  • 역 호환성을 위해 Next.js는 항상 /public/public/index로 간주한다. 따라서 /public/:path와 같은 matcher가 일치한다.

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


NextResponse

NextResponse API를 사용하면 다음과 같은 작업을 수행할 수 있다.

  • 들어오는 요청을 다른 URL로 redirect
  • 지정된 URL을 표시하여 responserewrite
  • API Route, getServerSidePropsrewrite 대상을 위해 요청 헤더를 설정한다.
  • 응답 쿠키를 설정한다.
  • 응답 헤더를 설정한다.

Middleware에서 응답을 생성하려면 다음과 같은 방법을 사용할 수 있다.

  • 응답을 생성하는 라우트(Page 또는 Edge API Route)로 rewrite
  • 직접 NextResponse를 반환.


쿠키 사용

쿠키는 일반적인 헤더이다. 요청에서는 쿠키 헤더에 저장되며, 응답에서는 Set-Cookie 헤더에 저장된다.

Next.js는 NextRequestNextResponse쿠키 확장을 통해 이러한 쿠키에 쉽게 액세스하고 조작할 수 있는 편리한 방법을 제공한다.

들어오는 요청에 대해서는, cookies에 다음과 같은 메서드가 있다.

get, getAll, set, delete, has 메서드를 사용하여 쿠키의 존재 여부를 확인할 수 있으며, clear 메서드를 사용하여 모든 쿠키를 제거할 수 있다.

나가는 응답에 대해서는, cookies에 다음과 같은 메서드가 있다.

get, getAll, set, delete 메서드를 사용할 수 있다.

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')?.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;
}


헤더 설정

NextResponse API를 사용하여 요청 및 응답 헤더를 설정할 수 있다.

요청 헤더 설정은 Next.js v13.0.0 이상에서 사용 가능하다.

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

참고로 백엔드 웹 서버 구성에 따라 431 Request Header Fields Too Large 오류가 발생할 수 있으므로 큰 헤더를 설정하는 것은 권장되지 않는다.



응답 생성

Middleware에서 Response 또는 NextResponse 인스턴스를 반환하여 직접 응답을 보낼 수 있다.

이 기능은 Next.js v13.1.0 이상에서 사용 가능하다.

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


Middleware 플래그

Next.js의 v13.1에서 Middleware에 대한 두 가지 추가 플래그인 skipMiddlewareUrlNormalizeskipTrailingSlashRedirect가 도입되었다.


skipTrailingSlashRedirect

skipTrailingSlashRedirect를 사용하면, Next.js의 기본 리디렉션을 비활성화하여 끝에 슬래시를 추가하거나 제거하는 데 대한 사용자 정의 처리를 Middleware 내부에서 수행할 수 있다. 이를 통해 일부 경로에서는 끝에 슬래시를 유지하고 다른 경로에서는 제거하도록 설정하여 쉽게 점진적인 마이그레이션을 수행할 수 있다.

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에 대한 완전한 제어가 필요한 몇 가지 고급 경우가 있으며, skipMiddlewareUrlNormalize을 사용하면 이를 가능하게 한다.

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
  // without the flag this would be normalized to /hello
}


Version 변경 내용

  • v13.1.0 : 고급 미들웨어 플래그 추가
  • v13.0.0 : 미들웨어가 요청 헤더, 응답 헤더를 수정하고 응답을 보낼 수 있음
  • v12.2.0 : 미들웨어 안정화
  • v12.0.9 : Edge 런타임에서 절대 URL을 강제로 사용하도록 변경
  • v12.0.0 : 미들웨어 (베타) 추가


[참고]
https://nextjs.org/docs/app/building-your-application/routing/middleware

profile
나는야 프린이

0개의 댓글