- 미들웨어는 요청이 완료되기전에 코드가 실행된다
- 수신된 요청을 기반으로 응답 수정/생성, 리다이렉트 등이 가능함
- 미들웨어는 캐싱된 내용과 경로가 일치하기 전에 실행된다
- 미들웨어를 사용하면 성능, 보안 및 UX 등이 크게 향상됨
- 아래는 미들웨어가 특히 효과적인 시나리오들임
- 유저가 특정 API 또는 페이지에 접근하기 전에 세션 쿠리를 활용해서 검증이 가능함
- locale, role 등 조건부로 리다이렉트 처리가 가능함
- request를 기반으로 API 경로 또는 페이지에 대한 경로를 동적으로 바꿀수ㅜ있음
- A/B 테스트, 기능 롤아웃, 레거시 경로를 지원할 수 있게됨
- 봇 요청을 탐지하고 해당 봇에 대해서 막을 수 있음
- page 또는 API에 접근하기전에 request data에 대한 분석이 가능함
- 원활한 기능 롤아웃 또는 테스트를 위해서 기능을 동적으로 활성화/비활성화가 가능함
하지만 미들웨어가 항상 최적의 접근방법이 아니라는것을 인식하는건 중요함
아래는 미들웨어를 피해야하는 시나리오임
- 미들웨어는 이런 데이터를 로딩하거나 조작하기 위해 설계된것이 아님
- 대신 라우트 핸들러나 서버측 유틸리티 내에서 수행해야됨
- 미들웨어는 가볍고 빠르게 작동해야함. 안그러면 페이지 로딩에 지연이 생길수도있음
- 무거운 계산 작업이나 장시간 진행되는 프로세스는 전용 라우트 핸들러에서 작업해야함
- 미들웨어는 기본적인 세션 작업은 관리할 수 있음
- 하지만 광범위한 세션 관리는 전용 인증서비스나 라우트 핸들러에서 관리해야함
- 미들웨어에서 DB에 직접 접근하는것은 권장되지 않음
- 마찬가지로 전용 라우트 핸들러나 서버 측 유틸리티 내에서 수행되야함
- 프로젝트 루트에
middware.ts
를 정의하여 미들웨어 사용이 가능함 - 미들웨어는 하나의 프로젝트에서 1개만 사용이 가능함
- 미들웨어가 복잡한 경우 기능별로 모듈화하여 불러온 뒤 사용하는게 좋음
- 단일 미들웨러를 통해서 충돌을 방지하고 여러 미들웨어 계층을 통해서 성능을 최적화함
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// 비동기처리가 필요한 경우 async/await 키워드 사용도 가능함
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL("/home", request.url));
}
// /about/:path*에 대해서만 미들웨어가 실행된다
export const config = {
matcher: "/about/:path*",
};
- 기본적으로 모든 라우터에 대해서 미들웨어가 실행됨
- 아래 설정을 통해서 특정 경로를 정확하게 타겟팅하거나 제외하기 위해서 matcher를 사용하는것이 중요함
- 매칭 순서
next.config.mjs
에서 적용된 헤더next.config.mjs
에서 적용된 리다이렉트- 미들웨어가 실행됨. 추가적인
rewrites
,redirects
등이 처리됨 - 파일 시스템 기반 라우트 체크전에 적용되는
rewrites
규칙 - 파일 시스템 기반 라우트가 체크됨
- 파일 시스템 기반 라우트가 체크 후 적용됨
/blog/[slug]
같은 동적 라우트가 처리됨- 마지막으로 적용되는 규칙으로 이전 단게에서 매칭되지 않는 요청이 처리됨
- 특정 경로에서만 미들웨어가 실행되게 할 수 있음
// src/middleware.ts
export const config = {
matcher: "/about/:path*",
};
// src/middleware.ts
export const config = {
matcher: ["/about/:path*", "/dashboard/:path*"],
};
matcher의 경우 정규표현식을 지원한다.
export const config = {
matcher: [
/*
* 아래 요소중에 하나라도 포함되면 미들웨어가 실행된다
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};
또한 missing
또는 has
배열로 데이터를 추가해서 바이패스가 가능하다
그리고 아래 들어가는 값들은 동적이 아닌 빌드타임에 정적으로 값을 가지고있어야 한다
export const config = {
matcher: [
/*
* 아래 요소중에 하나라도 포함되면 미들웨어가 실행된다
* - 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" }],
},
],
};
- 항상
/
으로 시작해야 한다 - 매개변수를 포함할 수 있음.
- /about/:path ->
/about/a
,/about/b
- 하지만
/about/a/c
는 불가능함
- /about/:path ->
- 와일드 마스크를 통한 표현도 가능함
/about/:path*
->/about/a/b/c
와 일치함
- 괄호 내부에 정규표현식 사용도 가능함
/about/(.*) ->
/about/:path` 와 동일함
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// /about → /about-2 redirect
if (request.nextUrl.pathname.startsWith("/about")) {
return NextResponse.rewrite(new URL("/about-2", request.url));
}
// /dashboard → /dashboard/user redirect
if (request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.rewrite(new URL("/dashboard/user", request.url));
}
}
redirect
를 활용한 다른 URL로 리다이렉트rewrites
를 활용한 지정 URL의 응답을 재구성API Route
,getServerSideProps
,rewrite
를 향한 request header 설정- 응답 쿠키, 헤더 설정
get
,getAll
,set
,delete
를 사용해서 쿠키 제어 가능has
를 사용해서 쿠키 존재여부 확인 가능delete
를 사용해서 모든 쿠키 삭제 가능
get
,getAll
,set
,delete
를 사용해서 쿠키 제어 가능
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// 요청에 대해 Cookie:nextjs=fast가 설정되어 있다고 가정
// 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
// 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: '/' }
// 응답 헤더에는 Set-Cookie:vercel=fast;path=/ 쿠키가 설정되어있음
return response;
}
NextResponse
를 통해서 헤더 세팅이 가능하다- Next.js v13.0.0 이후로 해당 기능을 사용할 수 있다
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// 기존 request.headers를 복사하고 새로운 x-hello-from-middleware1 헤더를 설정
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-hello-from-middleware1", "hello");
// NextResponse.rewrite를 통해서도 request header를 설정할 수 있음
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
});
// 새로운 response header 설정
response.headers.set("x-hello-from-middleware2", "hello");
return response;
}
- 미들웨어에서 CORS와 관련된 헤더도 설정이 가능함
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) {
// 기존 헤더에서 origin 가져오기
const origin = request.headers.get("origin") ?? "";
const isAllowedOrigin = allowedOrigins.includes(origin);
// preflight 요청 제어
const isPreflight = request.method === "OPTIONS";
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { "Access-Control-Allow-Origin": origin }),
...corsOptions,
};
return NextResponse.json({}, { headers: preflightHeaders });
}
// request 제어
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*",
};
Response
또는NextReponse
를 통해서 미들웨어에서 응답을 반환할 수 있다- Next.js v13.1.0 이후 버전부터 동작한다
import { NextRequest } 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 Response.json({ success: false, message: "authentication failed" }, { status: 401 });
}
}
NextFetchEvent
는 웹 기본 API인FetchEvent
를 상속한다- 그리고
waitUntil
메소드를 포함하고있다 waitUntil
메서드는Promise
를 인자로 받아서 작업이 완료될 때 까지 지속되게 해준다.- 해당 메서드는 백그라운드에서 작업을 할때 유용하다
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();
}
v13.1
에서 2개의 추가 플래그가 미들웨어에 포함됬다
- 트레일링 슬래쉬 처리를 커스터마이징 할때 사용한다
- 특정 경로는 트레일링 슬래시를 사용하지만 다른곳은 사용하지 않을때 등이 있다
- 대규모 사이트를 점진적으로 업데이트 하는 등 다양한 URL 요구사항을 가진 복잡한 사이트를 관리할때 유용하다고한다
// next.config.mjs
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();
}
// 트레일링 슬래쉬 활성화
if (!pathname.endsWith("/") && !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)) {
req.nextUrl.pathname += "/";
return NextResponse.redirect(req.nextUrl);
}
}
- URL을 정규화 할때 사용한다
- 에를 들어
/_next/data/build-id/hello.json
같은 내부 URL을/hello
로 정규화 한다 - 비활성화를 하게되면 정규화가 안된 원본 URL을 받아올 수 있다
// next.config.mjs
module.exports = {
skipMiddlewareUrlNormalize: true,
};
export default async function middleware({ req }: { req: NextRequest }) {
const { pathname } = req.nextUrl;
// GET /_next/data/build-id/hello.json
console.log(pathname);
// true로 설정하면 pathname이 /_next/data/build-id/hello.json가 된다
// 기본값인 경우 /hello가 된다
}
- 미들웨어는 오직 Edge 런타임만 지원한다
- Node.js 런타임은 지원하지 않음