안동민 개발노트

안동민 개발노트

Route Handler 생성HTTP 메서드 처리API Proxy외부 API와의 통합
본문 시작
  1. 홈
  2. 문서
  3. Next.js
  4. 11장 : Route Handler
  5. API Proxy
  1. Next.js
  2. API Proxy

API Proxy

Next.js 16 Proxy의 실행 위치와 matcher를 이해하고 인증·공통 헤더·요청 차단 경계를 설계합니다.

Next.js 16의 Proxy는 요청이 페이지나 라우트 핸들러에 도달하기 전에 실행되는 코드입니다.

공통 헤더 추가, 경로 변경, 간단한 인증 확인처럼 여러 경로에 반복되는 진입 규칙을 처리할 수 있습니다.

Proxy가 모든 업무 로직을 대신하는 것은 아닙니다.

데이터베이스를 조회해야 하는 소유권 판단과 실제 데이터 변경 권한은 라우트 핸들러에서 다시 확인합니다.


Proxy의 역할

Proxy는 요청 URL과 쿠키, 헤더를 읽고 다음 동작을 선택합니다.

  • 요청을 다음 처리 단계로 보냅니다.
  • 다른 URL로 리다이렉트합니다.
  • 브라우저 주소는 유지한 채 내부 대상을 다시 씁니다.
  • 오류나 JSON 응답을 즉시 반환합니다.

실행 범위가 넓을수록 모든 요청의 비용이 늘어납니다.

따라서 필요한 경로만 matcher로 선택합니다.


기본 Proxy 작성

이 프로젝트처럼 src/app을 사용하면 같은 src 아래의 proxy.ts에서 요청을 처리합니다. app을 루트에 두면 proxy.ts도 루트에 둡니다. Proxy 진입 파일은 하나이며, 아래 기본 예제와 뒤의 Auth.js 연결 예제는 대체 구현입니다.

src/proxy.ts
import { NextResponse, type NextRequest } from 'next/server';

export function proxy(request: NextRequest) {
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-request-path', request.nextUrl.pathname);

  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
}

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

:path*는 세그먼트가 없는 경우도 포함하므로 이 Proxy는 /api와 그 아래 경로에 실행됩니다.

Proxy에서 다음 서버로 전달하는 요청 헤더

Proxy에서 다음 서버로 전달하는 요청 헤더

요청 헤더 전달 방향Proxy는 request.headers 옵션으로 수정한 헤더를 다음 서버 처리에 전달한다.브라우저/api 경로 요청src/proxy.tsHeaders 복사x-request-path 설정Route Handler요청 헤더 읽기
요청 헤더 전달 방향브라우저의 요청이 Proxy의 헤더 복사를 거쳐 Route Handler로 간다.브라우저/api 경로 요청src/proxy.tsHeaders 복사x-request-path 설정Route Handler요청 헤더 읽기요청내부 요청

x-request-path는 다음 서버가 읽을 요청 헤더입니다. 브라우저에 보낼 응답 헤더는 별도로 설정합니다.


matcher 범위 설계

matcher는 Proxy가 실행될 경로를 정합니다.

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

/dashboard/:path*는 /dashboard 자체와 하위 경로를, /api/admin/:path*는 /api/admin 자체와 하위 경로를 포함합니다.

이미지와 빌드 산출물까지 모든 요청을 가로채면 불필요한 비용과 예외 처리가 늘어납니다.

보호할 URL 경계를 먼저 정한 뒤 가장 좁은 패턴을 사용합니다.


Auth.js Proxy 연결

인증이 필요한 여러 경로에는 src/auth.ts에서 만든 auth를 Proxy로 내보냅니다.

src/proxy.ts
export { auth as proxy } from '@/auth';

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

Proxy로 직접 내보낸 auth의 허용 여부는 중앙 설정의 authorized 콜백에서 판단합니다. 아래 역할 코드는 10장 권한 관리의 UserRole 타입 확장을 전제로 합니다. 기존 설정에 사용자 지정 로그인 페이지 등이 있다면 해당 설정도 유지해 결합합니다.

src/auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
  session: { strategy: 'jwt' },
  callbacks: {
    jwt({ token, user }) {
      if (user) {
        token.id = user.id ?? token.sub!;
        token.role = user.role ?? 'member';
      }

      return token;
    },
    session({ session, token }) {
      session.user.id = token.id;
      session.user.role = token.role;
      return session;
    },
    authorized({ auth }) {
      return Boolean(auth?.user);
    },
  },
});

현재 콜백은 로그인 여부만 검사합니다. GitHub 기본 프로필에 애플리케이션의 관리자 역할이 생기는 것은 아니므로, 관리자를 시험하려면 서버가 신뢰하는 역할 부여 경로도 필요합니다.

로그인하지 않은 페이지 요청은 로그인 화면으로 이동할 수 있습니다.

API 요청은 리다이렉트보다 명확한 401 또는 403 JSON 응답이 필요한 경우가 많습니다.

따라서 이 예제는 관리자 API를 Proxy matcher에 넣지 않고, 라우트 핸들러에서 auth로 요청을 감싸 응답을 직접 구분합니다.


라우트 핸들러에서 최종 검사

Proxy를 통과했다는 사실만 믿고 중요한 API의 검사를 생략하지 않습니다.

신고 게시물 조회 부분은 아래 저장소 대역으로 구성합니다. 인증 설정과 역할 타입 확장은 앞의 코드가 준비되어 있어야 합니다.

src/repositories/posts.ts
export type ReportedPostSummary = {
  id: string;
  title: string;
  reportCount: number;
};

const reportedPosts: ReportedPostSummary[] = [
  { id: 'post-1', title: '검토가 필요한 게시물', reportCount: 3 },
];

export async function findReportedPosts(): Promise<ReportedPostSummary[]> {
  return reportedPosts;
}

운영 코드에서는 같은 반환 계약을 유지한 채 메모리 배열을 데이터베이스 조회로 교체합니다.

src/app/api/admin/posts/route.ts
import { auth } from '@/auth';
import { findReportedPosts } from '@/repositories/posts';

export const GET = auth(async (request) => {
  const user = request.auth?.user;

  if (!user) {
    return Response.json({ message: '로그인이 필요합니다.' }, { status: 401 });
  }

  if (user.role !== 'admin') {
    return Response.json({ message: '접근 권한이 없습니다.' }, { status: 403 });
  }

  const posts = await findReportedPosts();
  return Response.json(posts);
});

현재의 불리언 authorized 콜백과 auth(handler) 구성에서는 래퍼가 제공한 request.auth를 핸들러가 검사해 401·403을 구분합니다. 콜백을 직접 Response를 반환하도록 바꾸면 그 응답이 우선할 수 있으므로 변경한 인증 설정과 API 계약을 함께 확인합니다.


리다이렉트와 rewrite

NextResponse.redirect()는 다른 URL을 가리키는 HTTP 응답을 만듭니다. 페이지 탐색에서 따라가면 주소창도 바뀌지만, fetch가 리다이렉트를 따라가는 것만으로 현재 페이지 주소가 바뀌지는 않습니다.

로그인하지 않은 사용자를 로그인 페이지로 보낼 때 사용할 수 있습니다.

NextResponse.rewrite()는 브라우저 주소를 유지하고 내부에서 다른 페이지나 핸들러를 실행합니다.

점검 화면이나 지역별 콘텐츠를 내부적으로 선택할 때 유용합니다.

API 오류를 로그인 HTML로 rewrite하면 클라이언트가 응답 형식을 오해할 수 있습니다.

API는 상태 코드와 JSON 계약을 유지하는 편이 안전합니다.


Proxy 운영 기준

Proxy에서는 긴 데이터베이스 조회와 외부 API 호출을 피합니다.

실행 경로는 matcher로 좁게 제한합니다.

인증 여부처럼 빠른 진입 판단만 수행하고 복잡한 업무 권한은 데이터 경계에서 확인합니다.

로그에 쿠키와 토큰 원문을 남기지 않습니다.

HTTP 메서드 처리

이전 페이지

외부 API와의 통합

다음 페이지

이 페이지의 목차

Proxy의 역할기본 Proxy 작성matcher 범위 설계Auth.js Proxy 연결라우트 핸들러에서 최종 검사리다이렉트와 rewriteProxy 운영 기준