Next.js Middleware·Proxy 설계 가이드: 인증·리다이렉트·A/B 테스트를 어디까지 맡길까
결론: Proxy는 ‘요청 전 라우팅 경계’이지 만능 인증 계층이 아닙니다
Next.js App Router에서 proxy.ts는 브라우저 요청이 실제 화면이나 API 로직에 도달하기 전에, 경로·쿠키·헤더·언어·실험군 같은 요청 정보를 보고 빠르게 방향을 정하는 계층입니다. 로그인하지 않은 사용자를 /login으로 보내거나, locale prefix가 없는 첫 방문자를 /ko 또는 /en으로 보내거나, 캠페인 쿠키에 따라 랜딩 페이지를 rewrite하는 작업에는 잘 맞습니다.
반대로 관리자 권한, 조직별 데이터 접근, 결제 플랜별 기능 제한, 개인정보 조회 권한처럼 ‘틀리면 보안 사고가 되는 판단’은 Proxy에만 두면 안 됩니다. Proxy에서 1차로 걸러도, 실제 데이터가 나가는 지점인 Server Component, Data Access Layer, Route Handler, Server Action에서 반드시 다시 검증해야 합니다. 운영 관점의 한 줄 기준은 이렇습니다. Proxy는 빠른 분기와 UX 보호, 백엔드와 DAL은 최종 권한 판단입니다.

왜 Middleware가 Proxy로 바뀌었나
Next.js 16 전후의 핵심 변화는 단순한 파일명 변경보다 ‘개념 정리’에 가깝습니다. 과거 middleware.ts라는 이름은 Express.js의 middleware처럼 애플리케이션 내부 로직 사이에 끼어드는 범용 계층으로 오해되기 쉬웠습니다. 하지만 Next.js의 이 기능은 렌더링 코드 안쪽이 아니라 네트워크 요청 앞단에서 실행됩니다. 그래서 공식 문서는 middleware 파일 규칙을 deprecated하고 proxy로 이름을 바꾸면서, 이 기능이 앱 앞단의 네트워크 경계에 가깝다는 점을 분명히 했습니다.
실무에서는 다음 세 가지를 먼저 기억하면 됩니다.
proxy.ts또는proxy.js는 프로젝트 루트 또는src아래,app과 같은 레벨에 둡니다.- 프로젝트당 하나의 Proxy 파일만 지원됩니다. 다만 내부 로직은
authProxy.ts,localeProxy.ts,experimentProxy.ts처럼 모듈로 나눠 가져올 수 있습니다. export function proxy(request)또는 default export를 사용할 수 있으며, 기존middleware.ts에서 이전할 때는 공식 codemod를 활용할 수 있습니다.
중요한 변화도 있습니다. Next.js 16 기준 Proxy는 Node.js runtime을 사용하며 Proxy 파일 안에서 runtime 옵션을 설정하는 방식은 사용할 수 없습니다. 기존 프로젝트가 Edge Runtime 전제의 auth 라이브러리나 세션 라이브러리를 사용했다면, 파일명만 바꾸지 말고 런타임 호환성부터 확인해야 합니다.
요청은 어떤 순서로 처리되나
Proxy 설계에서 자주 놓치는 지점은 실행 순서입니다. Next.js의 요청 처리 흐름에서 next.config.js의 headers와 redirects가 먼저 실행되고, 그 다음 Proxy가 실행됩니다. 이후 beforeFiles rewrites, 파일 시스템 라우트, 동적 라우트, fallback rewrites 순서로 이어집니다. 따라서 모든 리다이렉트를 Proxy에 넣는 설계는 처음부터 과합니다.

예를 들어 /old-service를 /new-service로 영구 이동시키는 단순 URL 변경은 next.config.js의 redirects가 우선입니다. 쿠키, 로그인 상태, 국가, 언어, 캠페인 파라미터처럼 요청별 조건이 필요할 때 Proxy를 고려합니다. 이 구분만 해도 Proxy 파일이 비대해지는 문제를 상당히 줄일 수 있습니다.
Proxy는 ‘모든 요청에 한 번쯤 지나가는 코드’가 될 수 있습니다. 그래서 로직을 넣는 것보다 먼저, 어떤 경로에서 아예 실행하지 않을지 matcher로 줄이는 설계가 필요합니다.
Proxy에 넣을지 말지 결정하는 실무 기준
창업자, PM, 마케터가 Next.js 프로젝트를 검수할 때는 코드보다 ‘정책의 위치’를 먼저 봐야 합니다. 아래 표는 SaaS, 랜딩, 관리자 화면에서 자주 나오는 요구사항을 기준으로 정리한 판단표입니다.

| 요구사항 | 우선 위치 | Proxy 사용 여부 | 운영 판단 기준 |
|---|---|---|---|
| 오래된 URL을 새 URL로 이동 | next.config.js redirects | 대체로 불필요 | 요청별 조건이 없고 SEO상 영구·임시 이동만 필요하면 config가 단순합니다. |
| 로그인하지 않은 사용자의 대시보드 접근 차단 | Proxy + Server Component/DAL | 적합 | Proxy는 세션 쿠키 존재 여부로 /login 리다이렉트, 실제 데이터 접근은 DAL에서 재검증합니다. |
| 관리자 권한 확인 | DAL, Route Handler, Server Action | 1차 UX gate만 | DB 조회가 필요한 role·permission 판단은 최종 데이터 접근 지점에서 처리합니다. |
| 국가·언어별 첫 진입 분기 | Proxy + app/[lang] | 적합 | locale prefix가 없을 때만 redirect하고, 이미 선택된 언어 URL은 그대로 둡니다. |
| 다국어 콘텐츠 렌더링 | Server Component, dictionary, CMS | 부적합 | 번역 데이터 로딩과 화면 렌더링은 Proxy가 아니라 라우트 내부에서 처리합니다. |
| A/B 테스트 랜딩 분기 | Proxy rewrite + 쿠키 | 조건부 적합 | 첫 배정만 Proxy에서 하고, 이후에는 쿠키로 고정해야 분석이 흔들리지 않습니다. |
| 가격제 플랜별 기능 제한 | 백엔드, DAL, API | 보조만 | 플랜 권한은 변동성이 크고 데이터 접근과 연결되므로 Proxy만 믿으면 위험합니다. |
| CORS 처리 | Route Handler 또는 Proxy | 범위에 따라 | 특정 API만 대상이면 Route Handler가 명확하고, 넓은 패턴이면 Proxy matcher로 제한합니다. |
| 보안 헤더 설정 | next.config.js headers, 플랫폼, Proxy | 상황별 | 정적이고 전역적인 헤더는 config나 플랫폼에서, 요청별 헤더 조정은 Proxy에서 처리합니다. |
인증: Proxy는 로그인 화면으로 보내는 문지기까지만
SaaS에서 가장 흔한 요구는 /dashboard, /settings, /billing 같은 경로를 로그인 사용자에게만 보여주는 것입니다. 이때 Proxy는 유용합니다. 요청 쿠키에 세션 토큰이 없으면 렌더링을 시작하기 전에 /login?next=/dashboard로 보낼 수 있기 때문입니다. 사용자는 빈 화면을 보지 않고, 서버는 불필요한 화면 렌더링을 줄일 수 있습니다.
하지만 여기서 멈추면 위험합니다. 쿠키가 있다는 사실은 ‘무언가를 들고 왔다’는 뜻이지, 그 세션이 유효하고 이 사용자가 해당 조직의 데이터를 볼 수 있다는 뜻은 아닙니다. 특히 관리자 화면, 팀 단위 SaaS, 결제 플랜이 있는 서비스에서는 Proxy의 낙관적 체크와 데이터 접근 계층의 보안 체크를 분리해야 합니다. 세션 만료, 계정 정지, 조직 탈퇴, 권한 회수는 Proxy에서 캐주얼하게 처리할 일이 아닙니다.
토큰 갱신과 세션 지속 전략까지 함께 설계해야 한다면 Refresh Token 설계 기준을 먼저 정리한 뒤 Proxy 정책을 붙이는 편이 안전합니다. Proxy는 인증 시스템의 출발점이 아니라, 이미 설계된 세션 정책을 요청 라우팅에 반영하는 얇은 계층이어야 합니다.
import { NextResponse, type NextRequest } from 'next/server'
const protectedPrefixes = ['/dashboard', '/admin']
export function proxy(req: NextRequest) {
const { pathname } = req.nextUrl
const session = req.cookies.get('session')?.value
const needsLogin = protectedPrefixes.some((prefix) => pathname.startsWith(prefix))
if (needsLogin && !session) {
const url = new URL('/login', req.url)
url.searchParams.set('next', pathname)
return NextResponse.redirect(url)
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
}위 코드는 방향을 설명하기 위한 단순 예시입니다. 실제 운영에서는 session 값의 암호화, 만료, 서명 검증, refresh 정책, CSRF 정책, 서버 액션 재검증을 별도로 다뤄야 합니다.
관리자 화면: URL 차단보다 ‘행위 권한’이 중요합니다
관리자 화면은 일반 대시보드보다 기준이 엄격해야 합니다. /admin 접근을 Proxy에서 막는 것은 좋지만, 그 자체로 관리 권한이 보장되지는 않습니다. 운영 서비스에서 실제 사고는 ‘관리자 페이지가 보였다’보다 ‘관리자 API나 Server Action이 직접 호출됐다’에서 발생하는 경우가 많습니다.
Next.js App Router에서는 Server Actions와 Route Handlers가 별도 진입점처럼 동작할 수 있습니다. 따라서 삭제, 승인, 결제 상태 변경, 회원 권한 변경 같은 mutation은 각각의 액션 내부에서 세션과 권한을 다시 확인해야 합니다. 화면에서 버튼을 숨기는 클라이언트 로직은 편의 기능일 뿐 보안 장치가 아닙니다.
실무 기준은 다음과 같습니다.
- Proxy: 로그인하지 않은 사용자를 관리자 화면에 들어오기 전에 리다이렉트합니다.
- Server Component: 관리자 전용 UI를 렌더링하기 전 role을 확인합니다.
- DAL: DB 조회 전에 조직, 리소스 소유권, 관리자 권한을 검증합니다.
- Server Action/Route Handler: 모든 변경 작업에서 인증과 인가를 다시 확인합니다.
- 감사 로그: 권한 변경, 삭제, 승인 같은 행위는 누가 언제 무엇을 했는지 남깁니다.
RSC와 서버 데이터 접근 위치를 함께 점검해야 한다면 React Server Components 성능 최적화 기준도 같이 보면 Proxy와 서버 렌더링의 책임 경계를 잡는 데 도움이 됩니다.
지역화와 SEO: 언어 결정은 Proxy, 콘텐츠 품질은 라우트 내부
다국어 랜딩이나 글로벌 SaaS에서는 사용자의 브라우저 Accept-Language 헤더를 보고 첫 페이지를 /ko, /en, /ja로 보낼 수 있습니다. 이 작업은 Proxy가 잘하는 일입니다. 아직 locale이 없는 URL에 들어온 요청을 보고, 지원 언어 목록과 기본 언어 정책에 따라 한 번 redirect하면 됩니다.
주의할 점은 ‘매 요청마다 언어를 다시 결정하지 않는 것’입니다. 사용자가 이미 /en/pricing에 들어왔거나 언어 선택 쿠키가 있다면 그것을 우선해야 합니다. 그렇지 않으면 사용자가 공유한 URL, 검색엔진이 수집한 URL, 광고 캠페인 URL이 계속 흔들릴 수 있습니다. 다국어 사이트에서는 Proxy보다 URL 정책, canonical, hreflang, sitemap, 번역 품질이 SEO에 더 큰 영향을 줍니다. 이 부분은 Next.js App Router SEO 구조와 함께 설계하는 편이 좋습니다.
A/B 테스트와 캠페인 랜딩: rewrite는 좋지만 무작위는 오래 두면 안 됩니다
마케팅팀은 종종 같은 URL에서 히어로 카피, 가격표, CTA 위치를 바꿔 테스트하고 싶어 합니다. Proxy에서 쿠키가 없는 첫 방문자에게 A 또는 B 실험군을 배정하고, 내부적으로 /landing-a 또는 /landing-b로 rewrite하는 방식은 실무에서 사용할 수 있습니다. URL은 유지하면서 서버 쪽 분기를 만들 수 있기 때문입니다.
다만 A/B 테스트를 Proxy에 넣을 때는 네 가지를 반드시 정해야 합니다.
- 고정성: 첫 배정 후 쿠키로 실험군을 고정해야 재방문과 전환 분석이 일관됩니다.
- 봇 처리: 검색엔진과 소셜 미리보기 봇에 어떤 버전을 보여줄지 정책이 필요합니다.
- 종료 조건: 실험이 끝난 뒤 승자 페이지를 정식 라우트로 승격하고 Proxy 로직을 제거해야 합니다.
- 개인정보: 국가, 조직, 광고 파라미터를 조합해 과도한 식별자를 만들지 않도록 주의해야 합니다.
Proxy의 waitUntil은 간단한 analytics 이벤트 전송처럼 응답을 막지 않아도 되는 작업에 활용할 수 있지만, 결제·회원가입·권한 변경 같은 핵심 업무 처리를 배경 작업처럼 넣어서는 안 됩니다.
헤더와 쿠키: 작게 전달하고, 민감정보를 싣지 마세요
Proxy에서는 요청 헤더를 upstream으로 전달하거나 응답 헤더와 쿠키를 설정할 수 있습니다. 예를 들어 x-locale, x-experiment, x-request-id처럼 서버 컴포넌트나 API에서 참고할 값을 넣을 수 있습니다. 하지만 헤더는 모든 요청에 붙을 수 있고, 서버나 프록시 설정에 따라 크기 제한에 걸릴 수 있습니다. 공식 문서도 큰 헤더를 피하라고 안내합니다.
운영 기준은 단순합니다. 헤더에는 ‘라우팅과 관측에 필요한 작은 힌트’만 싣고, 사용자 권한·개인정보·결제 상태처럼 신뢰해야 하는 값은 서버에서 다시 조회하십시오. 특히 클라이언트가 보낸 헤더, query string, 쿠키 값은 변조 가능하다는 전제로 다뤄야 합니다.
Proxy 마이그레이션 체크리스트

기존 middleware.ts가 있는 프로젝트라면 파일명만 바꾸기 전에 아래 순서로 점검하는 것이 좋습니다.
- 현재 로직 분류: 리다이렉트, 인증, 지역화, 실험, 헤더, 로깅, CORS를 항목별로 나눕니다.
- 정적 redirect 분리: 요청별 조건이 없는 URL 이동은
next.config.js redirects로 옮길 수 있는지 확인합니다. - 인증 재검증 위치 확인: Proxy에 있는 권한 판단이 DAL, Route Handler, Server Action에도 반복되어 있는지 봅니다.
- matcher 축소:
api,_next/static,_next/image,favicon.ico,sitemap.xml,robots.txt등 제외 대상을 정리합니다. - prefetch 확인: App Router prefetch 요청에서 불필요한 DB 조회나 analytics 오염이 생기지 않는지 테스트합니다.
- 런타임 호환성: auth, crypto, database client, session library가 Proxy의 Node.js runtime과 맞는지 확인합니다.
- 캐시 기대 제거: Proxy 내부 fetch에
next.revalidate나 tag 기반 캐시 효과를 기대하지 않습니다. - 단위 테스트: 주요 URL, 쿠키, 헤더 조건별로 Proxy가 실행되는지 테스트합니다.
- 배포 환경 테스트: Vercel, Docker, 자체 Node 서버, reverse proxy 앞단에서 쿠키와 헤더가 동일하게 전달되는지 확인합니다.
- 롤백 계획: 로그인, 결제, 관리자 화면처럼 영향이 큰 경로는 feature flag 또는 빠른 rollback 절차를 둡니다.
SaaS·랜딩·관리자 화면의 추천 구조
초기 SaaS나 정부지원 MVP에서 Next.js를 쓸 때는 다음처럼 책임을 나누면 유지보수가 쉬워집니다.
| 영역 | 추천 책임 | 예시 |
|---|---|---|
next.config.js | 정적인 headers, redirects | 구 URL 이동, 보안 헤더, legacy path 정리 |
proxy.ts | 요청 전 빠른 분기 | 로그인 쿠키 유무, locale 없는 첫 진입, 실험군 쿠키 배정 |
| Server Components | 화면 렌더링 전 데이터 조회와 조건부 UI | 사용자 대시보드, 관리자 위젯, 플랜별 안내 |
| DAL | 데이터 접근과 권한 검증의 중심 | 조직 소속 확인, DTO 반환, 민감 필드 제외 |
| Route Handlers | 외부 호출 가능한 API 보안 | 웹훅, 파일 업로드, 외부 연동 API |
| Server Actions | 폼 제출과 mutation | 회원 정보 수정, 관리자 승인, 설정 저장 |
| 관측성 | 로그, trace, analytics | 리다이렉트 원인, 실험군 배정, 인증 실패 패턴 |
이 구조를 잡지 않고 Proxy에 모든 조건을 넣으면, 출시 직후에는 빨라 보이지만 권한 정책이 늘어날수록 디버깅이 어려워집니다. 특히 B2B SaaS는 고객사별 권한, 관리자 역할, 결제 상태, 데이터 보존 정책이 뒤늦게 붙는 경우가 많습니다. 초기에 Proxy를 얇게 유지하는 것이 장기적으로 비용을 줄입니다.
의사결정자가 개발 전에 물어야 할 질문
비개발 의사결정자라면 구현 방식보다 아래 질문에 먼저 답해야 합니다.
- 로그인하지 않은 사용자는 어느 URL까지 볼 수 있는가?
- 로그인했지만 결제하지 않은 사용자는 어느 데이터까지 접근 가능한가?
- 관리자는 모든 고객사 데이터를 볼 수 있는가, 자기 조직만 볼 수 있는가?
- 언어와 국가는 URL, 쿠키, 브라우저 설정 중 무엇을 우선하는가?
- A/B 테스트는 검색엔진과 광고 랜딩 분석에 어떤 영향을 주는가?
- 권한이 바뀐 사용자는 몇 초 안에 접근이 차단되어야 하는가?
- Proxy에서 리다이렉트된 요청을 로그로 추적할 수 있는가?
AgentMit는 BizMit 기반 SaaS, 자동화 시스템, 관리자 대시보드, 정부지원 MVP를 설계할 때 Proxy를 별도 기술 팁으로만 보지 않습니다. 랜딩, 로그인, 관리자, API, 데이터 접근 계층, 배포 환경이 한 번에 맞물려야 운영 중 문제가 줄어들기 때문입니다. 이미 Next.js로 개발 중인데 인증·다국어·캠페인 랜딩·관리자 권한이 뒤섞여 있다면, BizMit 구현 범위를 참고해 구조를 먼저 정리한 뒤 개발 범위를 확정하는 것을 권합니다.
참고한 공식 문서
- Next.js Proxy Getting Started
- Next.js proxy.js File Convention
- Next.js Version 16 Upgrade Guide
- Next.js Authentication Guide
- Next.js Data Security Guide
- Next.js Internationalization Guide
- Next.js redirects Configuration
FAQ
1. Next.js 16에서 middleware.ts를 계속 써도 되나요?
기존 프로젝트가 즉시 동작하지 않는다고 단정할 문제는 아니지만, 신규 개발과 주요 리팩터링은 proxy.ts 기준으로 맞추는 편이 좋습니다. 마이그레이션 전에는 파일명, 함수명, skipMiddleware 계열 설정명, runtime 호환성을 함께 점검하세요.
2. 로그인 여부와 관리자 권한을 Proxy에서만 검사해도 되나요?
안 됩니다. Proxy는 세션 쿠키 존재 여부를 보고 빠르게 리다이렉트하는 1차 gate로 쓰고, 실제 권한 검증은 DAL, Route Handler, Server Action에서 다시 해야 합니다. 특히 관리자 작업과 데이터 변경은 각 액션 내부에서 검증해야 합니다.
3. A/B 테스트를 Proxy rewrite로 처리하면 SEO에 문제가 생기나요?
실험 범위와 기간을 제한하고 canonical, 봇 처리, 실험 종료 후 정리 기준을 정하면 사용할 수 있습니다. 다만 같은 URL에 핵심 콘텐츠가 장기간 무작위로 바뀌면 검색 노출과 전환 분석이 흔들릴 수 있습니다.
4. 다국어 사이트에서 Accept-Language를 매 요청마다 읽어 리다이렉트해도 되나요?
보통은 첫 진입에서만 locale을 결정하고, 이후에는 명시적 URL이나 사용자의 언어 선택 쿠키를 우선합니다. 이미 /ko, /en 같은 경로가 있는 요청을 다시 바꾸면 공유 URL과 검색엔진 수집이 불안정해질 수 있습니다.
5. Vercel이 아닌 자체 서버나 Docker에서도 Proxy를 쓸 수 있나요?
Node.js 서버와 Docker 배포는 공식적으로 지원되는 범주에 들어가지만, static export는 지원되지 않습니다. 자체 배포에서는 reverse proxy, 쿠키 전달, 헤더 크기 제한, 로그 수집, adapter 동작을 실제 운영 환경에서 테스트해야 합니다.

