Next.js의 Proxy 알아보기

김소연·2026년 2월 9일
post-thumbnail

Proxy란

기본 개념

Proxy는 클라이언트와 서버 사이에서, 중계 역할을 수행하는 서버를 말한다.
클라이언트는 직접 서버와 통신하지 않고, Proxy 서버를 거쳐 요청을 보낸다.
Proxy는 요청을 검사하거나 수정하고, 다른 서버로 전달하거나, 응답을 가공하여 다시 돌려준다

Proxy의 목적

요청이 실제 서버에 도달하기 전에 제어할 수 있다는 점에서, 다음과 같은 목적으로 사용된다.

  • 인증/인가 처리
  • 요청 로깅
  • 캐싱
  • 라우팅 분기
  • API 통합 (BFF 패턴)
  • A/B 테스트

Next.js의 Proxy

Next.js 16부터 proxy.js라는 새로운 파일 컨벤션이 도입되었다.

proxy.ts는 라우팅과 렌더링 이전에 실행되는 요청 처리 레이어이다.

Browser → Proxy → Routing → Rendering

해당 파일은 Proxy를 작성하고, 요청이 완료되기 전에 서버에서 코드를 실행하는 데 사용된다. Proxy는 라우트가 렌더링되기 전에 실행된다.

  • proxy.ts(또는 proxy.js) 파일은 pages 또는 app 폴더와 같은 레벨에 위치해 있어야 한다.
  • 파일 안에는 proxy라는 함수를 export해야 하고, matcher에 해당하는 요청에 대해서 적용된다.

더 자세한 내용 보기 : https://nextjs.org/docs/app/api-reference/file-conventions/proxy

import { NextResponse } from 'next/server'
import type { 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))
}
 
// Alternatively, you can use a default export:
// export default function proxy(request: NextRequest) { ... }
 
export const config = {
  matcher: '/about/:path*',
}

이 코드는, /about/*로 들어오는 모든 요청을 /home으로 리디렉션한다.

Proxy와 Middleware, Rewrites

Next.js 15까지는 Middleware(middleware.js)가 사용되었고, 현재는 Proxy로 대체되었다.

비슷한 역할을 하는 것으로, next.config.js의 rewrites가 있다.
rewrites는 단순히 요청 경로를 다른 경로로 매핑하는 정적인 라우팅 규칙이다.

module.exports = {
  async rewrites() {
    return [
      {
        source: '/about',
        destination: '/',
      },
    ]
  },
}

반면 Proxy는 요청을 기반으로 동적인 행동을 하고자 할 때 적합하다

  • 요청을 직접 보고 처리 가능
  • 특정 조건에 따라 리다이렉트, 요청 헤더/쿠키를 보고 rewrite 또는 응답 구성

Proxy 활용하기

1. 인증(Authorization)

cookies/headers를 확인하여 인증되지 않은 요청을 차단하는데 활용할 수 있다.

로그인하지 않은 사용자가 로그인이 필요한 페이지 진입을 막고 로그인 페이지로 보낼 수 있다.

if (!request.cookies.get('sessionToken')) {
  return NextResponse.redirect('/login')
}

또는 인증되지 않은 요청에 대해 즉시 응답을 반환하도록 할 수 있다.

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

이러한 방식은 인증 로직을 중앙에서 관리할 수 있다는 장점이 있다.

  • 보호해야 할 모든 경로를 일관되게 제어 가능
  • 인증 실패 시 불필요한 렌더링/로직 실행을 막음
  • 보안 측면에서 안전

2. A/B 테스트

동적으로 라우팅할 수 있는 Proxy는 A/B 테스트에도 활용할 수 있다.

새롭게 개발한 API을 실제 사용자에 시험 적용하고자 할 때, 조건에 따라 새로운 API로 보내 일부 사용자만 사용해보도록 점진적으로 테스트해볼 수 있다.

if (featureFlag.isEnabled('newCheckout') && pathname.startsWith('/checkout')) {
  return NextResponse.rewrite(new URL('/new-checkout' + rest, request.url))
}

또는 랜덤하게 사용자에게 다른 페이지를 보여주어 어떤 UI가 효과적인지 테스트해볼 수도 있겠다.

if (Math.random() > 0.5) {
  return NextResponse.rewrite("/new-ui");
}

3. BFF(Backend For Frontend) / 마이크로 서비스 통합

Proxy는 BFF(Backend For Frontend) 계층을 구성하는 데에도 유용하게 활용할 수 있다.

예를 들어, 이커머스 서비스의 대시보드 화면에서 사용자 정보 API, 주문 API, 추천 상품 API를 호출해야 한다고 가정해보자.
프론트엔드가 이 API를 각각 호출하는 대신, Proxy을 통해 요청을 통합 처리하고 하나의 응답으로 리턴할 수 있다. 그러면 프론트엔드는 복잡한 서비스 구조를 알 필요 없이 하나의 API만 사용하면 되고, 서비스 구조가 변경되더라도 프론트엔드에는 최소한의 영향을 주는 유연한 아키텍처를 만들 수 있다.

또한 사이트를 /shop, /blog 등으로 분리해 각 경로를 별도의 앱/서비스가 담당하도록 하고자 할 때, 요청 경로나 조건에 따라 어느 서비스로 라우팅할지 동적으로 결정할 수도 있다.

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (pathname.startsWith('/shop')) {
    return NextResponse.rewrite('https://shop.example.com' + pathname);
  }

  if (pathname.startsWith('/blog')) {
    return NextResponse.rewrite('https://blog.example.com' + pathname);
  }

  return NextResponse.next();
}

유의할 점

Proxy는 라우팅 전 단계에서 간단하고 빠르게 요청을 처리하는 용도로 설계되었다. 따라서 무거운 데이터 처리나 복잡한 비즈니스 로직은 API 라우트나 백엔드 서비스에 두는 것이 좋다.


참고 자료

0개의 댓글