// frontend/proxy.ts — phase-15 인증 게이트. // Next 16에서 `middleware` 파일 규약은 deprecated → `proxy`로 개명됨(node_modules/next/.../proxy.md). // 기능은 동일(요청을 라우트 렌더 전에 가로채 리다이렉트). 데모 무손상이 최우선. // // 동작: // - NEXT_PUBLIC_AUTH_ENABLED !== "true" → 즉시 통과(NextResponse.next()). 데모/CI 영향 0. // - 활성 + 보호 라우트 + ari_session 쿠키 없음 → /login?next=<경로> 리다이렉트. // - /login, /_next, /api, 정적 자산은 matcher에서 제외되어 항상 통과. import { NextResponse, type NextRequest } from "next/server"; const AUTH_ENABLED = process.env.NEXT_PUBLIC_AUTH_ENABLED === "true"; const SESSION_COOKIE = "ari_session"; export function proxy(request: NextRequest) { // 게이트가 꺼져 있으면(기본값) 아무것도 하지 않는다 — 데모 100% 무손상. if (!AUTH_ENABLED) { return NextResponse.next(); } const { pathname } = request.nextUrl; // /login 은 언제나 허용(무한 리다이렉트 방지). matcher가 _next/api/정적은 이미 제외. if (pathname === "/login" || pathname.startsWith("/login/")) { return NextResponse.next(); } // 세션 쿠키가 있으면 통과(검증은 백엔드 GET /api/me·라우터 의존성이 수행). if (request.cookies.get(SESSION_COOKIE)) { return NextResponse.next(); } // 미인증 → /login?next=<원래 경로+쿼리> const next = pathname + request.nextUrl.search; const url = request.nextUrl.clone(); url.pathname = "/login"; url.search = `?next=${encodeURIComponent(next)}`; return NextResponse.redirect(url); } export const config = { // 보호 대상 = 앱 페이지 전부. 제외: api, _next/static, _next/image, 메타데이터, // /login, 그리고 확장자 있는 정적 자산(.svg/.png/.css/.js …). matcher: [ "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|login|.*\\..*).*)", ], };