파일 시스템 규칙: proxy.js
파일 시스템 규칙: proxy.js | Next.js
섹션 제목: “파일 시스템 규칙: proxy.js | Next.js”Source URL: https://nextjs.org/docs/app/api-reference/file-conventions/proxy
proxy.js
섹션 제목: “proxy.js”마지막 업데이트 2026년 2월 20일
참고:
middleware파일 규칙은 더 이상 사용되지 않으며proxy로 이름이 변경되었습니다. 자세한 내용은 Migration to Proxy를 참고하세요.
proxy.js|ts 파일은 Proxy를 작성하고 요청이 완료되기 전에 서버에서 코드를 실행할 때 사용됩니다. 그런 다음 들어오는 요청을 기준으로 응답을 재작성하거나 리디렉션하고, 요청 또는 응답 헤더를 수정하거나, 직접 응답을 반환하여 응답을 변경할 수 있습니다.
Proxy는 라우트가 렌더링되기 전에 실행됩니다. 인증, 로깅, 리디렉션 처리와 같은 사용자 정의 서버 측 로직을 구현할 때 특히 유용합니다.
알아두면 좋은 점:
Proxy는 렌더링 코드와 별도로 호출되도록 설계되었으며, 최적화된 경우 빠른 리디렉션/재작성 처리를 위해 CDN에 배포될 수 있으므로 공유 모듈이나 전역 상태에 의존해서는 안 됩니다.
Proxy에서 애플리케이션으로 정보를 전달하려면 headers, cookies, rewrites, redirects 또는 URL을 사용하세요.
프로젝트 루트 또는 필요한 경우 src 내부에 proxy.ts (또는 .js) 파일을 생성하여 pages 또는 app과 동일한 계층에 위치하도록 하세요.
pageExtensions를 .page.ts 또는 .page.js로 사용자 정의한 경우, 파일 이름을 각각 proxy.page.ts 또는 proxy.page.js로 지정하세요.
proxy.ts
JavaScriptTypeScript
import { NextResponse, NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside export function proxy(request: NextRequest) { return NextResponse.redirect(new URL('/home', request.url)) }
export const config = { matcher: '/about/:path*', }Exports
섹션 제목: “Exports”Proxy function
섹션 제목: “Proxy function”이 파일은 기본 내보내기 또는 proxy라는 이름의 단일 함수를 내보내야 합니다. 동일한 파일에서 여러 개의 proxy를 정의하는 것은 지원되지 않습니다.
proxy.js
// Example of default export export default function proxy(request) { // Proxy logic }Config object (optional)
섹션 제목: “Config object (optional)”선택적으로 Proxy 함수와 함께 config 객체를 내보낼 수 있습니다. 이 객체에는 Proxy가 적용되는 경로를 지정하는 matcher가 포함됩니다.
Matcher
섹션 제목: “Matcher”matcher 옵션을 사용하면 Proxy가 실행될 특정 경로를 지정할 수 있습니다. 다음과 같은 방식으로 경로를 지정할 수 있습니다.
- 단일 경로의 경우:
'/about'처럼 문자열을 직접 사용합니다. - 여러 경로의 경우: 배열을 사용해 여러 경로를 나열합니다. 예를 들어
matcher: ['/about', '/contact']는 Proxy를/about과/contact에 모두 적용합니다.
proxy.js
export const config = { matcher: ['/about/:path*', '/dashboard/:path*'], }또한 matcher 옵션은 정규식을 사용한 복잡한 경로 지정도 지원합니다. 예를 들어 정규식 matcher로 특정 경로를 제외할 수 있습니다.
proxy.js
export const config = { matcher: [ // Exclude API routes, static files, image optimizations, and .png files '/((?!api|_next/static|_next/image|.*\\.png$).*)', ], }이를 통해 포함하거나 제외할 경로를 정밀하게 제어할 수 있습니다.
matcher 옵션은 다음 키를 가진 객체 배열을 받을 수 있습니다.
source: 요청 경로와 매칭할 경로 또는 패턴입니다. 단순 경로 매칭을 위한 문자열이나 더 복잡한 매칭을 위한 패턴일 수 있습니다.locale(선택 사항):false로 설정하면 경로 매칭에서 로케일 기반 라우팅을 무시합니다.has(선택 사항): 헤더, 쿼리 파라미터, 쿠키 등 특정 요청 요소의 존재 여부에 따른 조건을 지정합니다.missing(선택 사항): 특정 헤더나 쿠키가 없는 경우와 같은 요청 요소 부재 조건에 초점을 맞춥니다.
proxy.js
export const config = { matcher: [ { source: '/api/:path*', locale: false, has: [ { type: 'header', key: 'Authorization', value: 'Bearer Token' }, { type: 'query', key: 'userId', value: '123' }, ], missing: [{ type: 'cookie', key: 'session', value: 'active' }], }, ], }source 경로 패턴은 다음을 의미합니다.
- 반드시
/로 시작해야 합니다. - 명명된 매개변수를 포함할 수 있습니다:
/about/:path는/about/a와/about/b에 매칭되지만/about/a/c에는 매칭되지 않습니다. - 명명된 매개변수에 수정자를 사용할 수 있습니다 (
:로 시작):/about/:path*는*가 _0개 이상_을 의미하므로/about/a/b/c와 매칭됩니다.?는 0개 또는 1개,+는 _1개 이상_을 의미합니다. - 괄호로 감싼 정규식을 사용할 수 있습니다:
/about/(.*)는/about/:path*와 동일합니다. - 경로 시작 부분에 고정됩니다:
/about은/about과/about/team에는 매칭되지만/blog/about에는 매칭되지 않습니다.
자세한 내용은 path-to-regexp 문서를 참고하세요.
알아두면 좋은 점:
matcher값은 빌드 시 정적 분석을 위해 상수여야 합니다. 변수와 같은 동적 값은 무시됩니다.- 하위 호환성을 위해 Next.js는 항상
/public을/public/index로 간주합니다. 따라서/public/:pathmatcher는 매칭됩니다.
Params
섹션 제목: “Params”request
섹션 제목: “request”Proxy를 정의할 때 기본 내보내기 함수는 단일 매개변수 request를 받습니다. 이 매개변수는 들어오는 HTTP 요청을 나타내는 NextRequest 인스턴스입니다.
proxy.ts
JavaScriptTypeScript
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) { // Proxy logic goes here }알아두면 좋은 점:
NextRequest는 Next.js Proxy에서 들어오는 HTTP 요청을 나타내는 타입이고,NextResponse는 HTTP 응답을 조작하고 반환하는 데 사용되는 클래스입니다.
NextResponse
섹션 제목: “NextResponse”NextResponse API로 다음 작업을 수행할 수 있습니다.
- 들어오는 요청을 다른 URL로
redirect - 지정한 URL을 표시하여 응답을
rewrite - API Routes,
getServerSideProps,rewrite대상에 대한 요청 헤더 설정 - 응답 쿠키 설정
- 응답 헤더 설정
Proxy에서 응답을 생성하는 방법은 다음과 같습니다.
- 응답을 생성하는 라우트(Page 또는 Route Handler)로
rewrite NextResponse를 직접 반환 (자세한 내용은 Producing a Response 참고)
알아두면 좋은 점: 리디렉션에는
NextResponse.redirect대신Response.redirect도 사용할 수 있습니다.
Execution order
섹션 제목: “Execution order”Proxy는 프로젝트의 모든 라우트에 대해 호출됩니다. 따라서 matchers를 사용해 특정 라우트를 정밀하게 지정하거나 제외하는 것이 중요합니다. 실행 순서는 다음과 같습니다.
next.config.js의headersnext.config.js의redirects- Proxy (
rewrites,redirects등) next.config.js의beforeFiles(rewrites)- 파일 시스템 라우트 (
public/,_next/static/,pages/,app/등) next.config.js의afterFiles(rewrites)- 동적 라우트 (
/blog/[slug]) next.config.js의fallback(rewrites)
Runtime
섹션 제목: “Runtime”Proxy는 기본적으로 Node.js 런타임을 사용합니다. Proxy 파일에서는 runtime config 옵션을 사용할 수 없습니다. Proxy에서 runtime config 옵션을 설정하면 오류가 발생합니다.
Advanced Proxy flags
섹션 제목: “Advanced Proxy flags”Next.js v13.1에서는 고급 사용 사례를 처리하기 위해 Proxy에 skipMiddlewareUrlNormalize와 skipTrailingSlashRedirect 두 가지 추가 플래그가 도입되었습니다.
skipTrailingSlashRedirect는 후행 슬래시를 추가하거나 제거하는 Next.js 리디렉션을 비활성화합니다. 이를 통해 Proxy 내부에서 일부 경로는 후행 슬래시를 유지하고 일부는 유지하지 않는 사용자 정의 처리가 가능하므로 점진적 마이그레이션을 더 쉽게 할 수 있습니다.
next.config.js
module.exports = { skipTrailingSlashRedirect: true, }proxy.js
const legacyPrefixes = ['/docs', '/blog']
export default async function proxy(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+)/) ) { return NextResponse.redirect( new URL(`${req.nextUrl.pathname}/`, req.nextUrl) ) } }skipMiddlewareUrlNormalize는 Next.js의 URL 정규화를 비활성화하여 직접 방문과 클라이언트 전환을 동일하게 처리할 수 있게 합니다. 일부 고급 사례에서는 이 옵션을 통해 원래 URL을 사용해 완전한 제어를 제공할 수 있습니다.
next.config.js
module.exports = { skipMiddlewareUrlNormalize: true, }proxy.js
export default async function proxy(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 }Examples
섹션 제목: “Examples”Conditional Statements
섹션 제목: “Conditional Statements”proxy.ts
JavaScriptTypeScript
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server'
export function proxy(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)) } }Using Cookies
섹션 제목: “Using Cookies”쿠키는 일반 헤더입니다. Request에서는 Cookie 헤더에 저장되고, Response에서는 Set-Cookie 헤더에 존재합니다. Next.js는 NextRequest와 NextResponse의 cookies 확장을 통해 이러한 쿠키에 접근하고 조작할 수 있는 편리한 방법을 제공합니다.
- 들어오는 요청의 경우
cookies는get,getAll,set,delete메서드를 제공합니다.has로 쿠키 존재 여부를 확인하거나clear로 모든 쿠키를 제거할 수 있습니다. - 나가는 응답의 경우
cookies는get,getAll,set,delete메서드를 제공합니다.
proxy.ts
JavaScriptTypeScript
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server'
export function proxy(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 }헤더 설정하기
섹션 제목: “헤더 설정하기”NextResponse API를 사용해 요청 및 응답 헤더를 설정할 수 있습니다(요청 헤더 설정은 Next.js v13.0.0부터 지원).
proxy.ts
JavaScriptTypeScript
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) { // Clone the request headers and set a new header `x-hello-from-proxy1` const requestHeaders = new Headers(request.headers) requestHeaders.set('x-hello-from-proxy1', 'hello')
// You can also set request headers in NextResponse.next const response = NextResponse.next({ request: { // New request headers headers: requestHeaders, }, })
// Set a new response header `x-hello-from-proxy2` response.headers.set('x-hello-from-proxy2', 'hello') return response }이 코드 스니펫에서 사용하는 방식은 다음과 같습니다.
NextResponse.next({ request: { headers: requestHeaders } })를 사용해requestHeaders를 업스트림에 전달합니다.- NOT
NextResponse.next({ headers: requestHeaders })(requestHeaders가 클라이언트에게 노출되므로 사용하지 마세요).
NextResponse headers in Proxy에서 더 알아보세요.
알아두면 좋아요: 지나치게 큰 헤더를 설정하면 백엔드 웹 서버 구성에 따라 431 Request Header Fields Too Large 오류가 발생할 수 있으니 주의하세요.
CORS
섹션 제목: “CORS”Proxy에서 CORS 헤더를 설정해 간단 요청과 프리플라이트 요청을 포함한 교차 출처 요청을 허용할 수 있습니다.
proxy.ts
JavaScriptTypeScript
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 proxy(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*', }알아두면 좋아요: Route Handlers에서 개별 라우트별로 CORS 헤더를 구성할 수도 있습니다.
응답 생성하기
섹션 제목: “응답 생성하기”Proxy에서 Response 또는 NextResponse 인스턴스를 반환해 직접 응답을 보낼 수 있습니다(이는 Next.js v13.1.0부터 지원).
proxy.ts
JavaScriptTypeScript
import type { NextRequest } from 'next/server' import { isAuthenticated } from '@lib/auth'
// Limit the proxy to paths starting with `/api/` export const config = { matcher: '/api/:function*', }
export function proxy(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 } ) } }부정 매칭
섹션 제목: “부정 매칭”matcher 구성은 정규식을 완전히 지원하므로 부정형 전방 탐색이나 문자 매칭을 사용할 수 있습니다. 특정 경로를 제외하고 모두 매칭하는 부정형 전방 탐색 예시는 다음과 같습니다.
proxy.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, sitemap.xml, robots.txt (metadata files) */ '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', ], }missing 또는 has 배열(또는 두 가지를 조합)을 사용해 특정 요청을 Proxy에서 건너뛸 수도 있습니다.
proxy.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, sitemap.xml, robots.txt (metadata files) */ { source: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', missing: [ { type: 'header', key: 'next-router-prefetch' }, { type: 'header', key: 'purpose', value: 'prefetch' }, ], },
{ source: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', has: [ { type: 'header', key: 'next-router-prefetch' }, { type: 'header', key: 'purpose', value: 'prefetch' }, ], },
{ source: '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', has: [{ type: 'header', key: 'x-present' }], missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }], }, ], }알아두면 좋아요 :
부정 매처 패턴에서
_next/data를 제외하더라도 Proxy는 여전히_next/data라우트에 대해 실행됩니다. 이는 보호 대상 페이지의 데이터 라우트를 실수로 보호하지 않는 보안 문제를 방지하기 위한 의도적인 동작입니다.
proxy.js
export const config = { matcher: '/((?!api|_next/data|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', }
// Proxy will still run for /_next/data/* routes despite being excludedwaitUntil 및 NextFetchEvent
섹션 제목: “waitUntil 및 NextFetchEvent”NextFetchEvent 객체는 기본 FetchEvent 객체를 확장하며 waitUntil() 메서드를 포함합니다.
waitUntil() 메서드는 프로미스를 인수로 받아 해당 프로미스가 완료될 때까지 Proxy의 수명을 연장합니다. 백그라운드 작업을 수행할 때 유용합니다.
proxy.ts
import { NextResponse } from 'next/server' import type { NextFetchEvent, NextRequest } from 'next/server'
export function proxy(req: NextRequest, event: NextFetchEvent) { event.waitUntil( fetch('https://my-analytics-platform.com', { method: 'POST', body: JSON.stringify({ pathname: req.nextUrl.pathname }), }) )
return NextResponse.next() }단위 테스트(실험적)
섹션 제목: “단위 테스트(실험적)”Next.js 15.1부터 next/experimental/testing/server 패키지가 Proxy 파일 단위 테스트를 돕는 유틸리티를 제공합니다. Proxy를 단위 테스트하면 원하는 경로에서만 실행되는지, 프로덕션에 도달하기 전에 사용자 정의 라우팅 로직이 정상 작동하는지를 확인할 수 있습니다.
unstable_doesProxyMatch 함수는 주어진 URL, 헤더, 쿠키에 대해 Proxy가 실행되는지 여부를 단언하는 데 사용할 수 있습니다.
import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
expect( unstable_doesProxyMatch({ config, nextConfig, url: '/test', }) ).toEqual(false)전체 Proxy 함수도 테스트할 수 있습니다.
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
const request = new NextRequest('https://nextjs.org/docs') const response = await proxy(request) expect(isRewrite(response)).toEqual(true) expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs') // getRedirectUrl could also be used if the response were a redirect플랫폼 지원
섹션 제목: “플랫폼 지원”| Deployment Option | Supported |
|---|---|
| Node.js server | 예 |
| Docker container | 예 |
| Static export | 아니요 |
| Adapters | 플랫폼별 |
Next.js를 셀프 호스팅할 때 Proxy를 구성하는 방법을 알아보세요.
Proxy로 마이그레이션
섹션 제목: “Proxy로 마이그레이션”변경 이유
섹션 제목: “변경 이유”middleware의 이름을 변경한 이유는 “middleware”라는 용어가 종종 Express.js 미들웨어와 혼동되어 목적이 잘못 해석되기 때문입니다. 또한 Middleware는 매우 강력해 사용을 부추길 수 있지만, 이 기능은 최후의 수단으로 사용할 것을 권장합니다.
Next.js는 개발자가 Middleware 없이도 목표를 달성할 수 있도록 더 나은 인체공학을 갖춘 API를 제공하는 방향으로 나아가고 있습니다. 이것이 middleware의 이름을 변경한 이유입니다.
왜 “Proxy”인지
섹션 제목: “왜 “Proxy”인지”Proxy라는 이름은 Middleware가 할 수 있는 일을 명확히 보여 줍니다. “proxy”라는 용어는 앱 앞에 네트워크 경계가 있음을 암시하는데, 이는 Middleware의 동작입니다. 또한 Middleware는 기본적으로 앱의 리전과 분리되어 클라이언트에 더 가까운 Edge Runtime에서 실행됩니다. 이러한 동작은 “proxy”라는 용어와 더 잘 맞으며 기능의 목적을 더 분명히 합니다.
마이그레이션 방법
섹션 제목: “마이그레이션 방법”다른 선택지가 없다면 몰라도 가능한 한 Middleware에 의존하지 않길 권장합니다. 우리의 목표는 Middleware 없이도 목표를 달성할 수 있도록 더 나은 인체공학을 지닌 API를 제공하는 것입니다.
“middleware”라는 용어는 종종 Express.js 미들웨어와 혼동되어 오용을 부추길 수 있습니다. 방향성을 분명히 하기 위해 파일 규칙을 “proxy”로 이름 변경하고 있습니다. 이는 Middleware에서 멀어져 과도한 기능을 분리하고 Proxy의 목적을 분명히 한다는 점을 강조합니다.
Next.js는 middleware.ts에서 proxy.ts로 마이그레이션할 수 있는 codemod를 제공합니다. 다음 명령으로 마이그레이션할 수 있습니다:
npx @next/codemod@canary middleware-to-proxy .codemod는 파일과 함수 이름을 middleware에서 proxy로 변경합니다.
// middleware.ts -> proxy.ts
- export function middleware() { + export function proxy() {버전 기록
섹션 제목: “버전 기록”| 버전 | 변경 내용 |
|---|---|
v16.0.0 | Middleware는 사용 중단(deprecated)되었으며 Proxy로 이름이 변경되었습니다 |
v15.5.0 | Middleware가 이제 안정(stable)된 Node.js 런타임을 사용할 수 있습니다 |
v15.2.0 | Middleware가 이제 실험(experimental) 단계에서 Node.js 런타임을 사용할 수 있습니다 |
v13.1.0 | 고급 Middleware 플래그가 추가되었습니다 |
v13.0.0 | Middleware가 요청 헤더와 응답 헤더를 수정하고 응답을 전송할 수 있게 되었습니다 |
v12.2.0 | Middleware가 안정화되었으며, 업그레이드 가이드를 참고하세요 |
v12.0.9 | Edge Runtime에서 절대 URL을 강제합니다 (PR) |
v12.0.0 | Middleware(베타)가 추가되었습니다 |
Proxy 더 알아보기
섹션 제목: “Proxy 더 알아보기”-
- 에 대한 NextResponseAPI 참고 자료.
보내기