Next.js 16 Middleware 완벽 가이드 2026: Edge Runtime, 인증 패턴, 면접 질문
Next.js 16 Middleware의 동작 원리, 인증 패턴, Edge Runtime 제한 사항과 해결 방법, 그리고 면접에서 자주 묻는 질문을 실용적인 코드 예제와 함께 설명합니다.

Next.js 16의 Middleware는 모든 요청이 서버에 도달하기 전에 실행되어 인증, 현지화, 요청 조작의 첫 번째 방어선 역할을 합니다. 프로덕션 애플리케이션 구축에 필수적이며 프론트엔드 면접에서 자주 등장하는 주제입니다.
Next.js 16 Middleware는 기본적으로 Edge Runtime에서 실행됩니다. 이는 사용자와 가까운 데이터 센터에서 실행되며 콜드 스타트가 밀리초 이하임을 의미합니다. 페이지가 렌더링되기 전에 수행해야 하는 인증 확인 및 리다이렉트에 이상적입니다.
Next.js 16 Middleware 동작 원리
Next.js 16의 Middleware는 요청이 라우트 핸들러나 페이지 컴포넌트에 도달하기 전에 가로챕니다. Middleware 파일은 프로젝트 루트(app/ 또는 pages/ 옆)에 배치해야 하며 NextRequest 객체를 받는 기본 함수를 내보내야 합니다.
Edge Runtime 제약으로 인해 Middleware에서는 fs나 Buffer와 같은 Node.js API를 사용할 수 없습니다. Cloudflare Workers와 유사한 V8 isolates에서 실행되어 글로벌 배포가 가능하지만 사용 가능한 API는 Web Standards로 제한됩니다.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Access request headers, cookies, and URL
const token = request.cookies.get('session')?.value
const pathname = request.nextUrl.pathname
// Log for debugging (visible in Vercel logs)
console.log(`Middleware: ${request.method} ${pathname}`)
// Continue to the route handler
return NextResponse.next()
}
// Configure which paths trigger middleware
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}matcher 설정은 어떤 경로가 Middleware를 트리거하는지 정의하는 정규식과 유사한 구문을 사용합니다. 이것이 없으면 정적 자산을 포함한 모든 요청에서 Middleware가 실행됩니다.
Next.js Middleware를 활용한 인증 패턴
Middleware가 인증에 뛰어난 이유는 페이지 코드가 실행되기 전에 동작하기 때문입니다. 유효한 세션이 없는 사용자는 보호된 콘텐츠를 잠시도 보지 못합니다. 이는 리다이렉트 전에 보호된 콘텐츠가 잠시 표시될 수 있는 클라이언트 사이드 인증 확인과 다릅니다.
다음 패턴은 세션 토큰을 검증하고 인증되지 않은 사용자를 리다이렉트합니다. 쿠키 또는 헤더에 세션 데이터를 저장하는 모든 인증 제공자와 통합할 수 있습니다.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const protectedPaths = ['/dashboard', '/settings', '/profile']
const authPaths = ['/login', '/signup']
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const sessionToken = request.cookies.get('session')?.value
// Check if path requires authentication
const isProtectedPath = protectedPaths.some(path =>
pathname.startsWith(path)
)
const isAuthPath = authPaths.some(path =>
pathname.startsWith(path)
)
// Validate session token (call auth service)
const isValidSession = sessionToken
? await validateSession(sessionToken)
: false
// Redirect unauthenticated users to login
if (isProtectedPath && !isValidSession) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
// Redirect authenticated users away from auth pages
if (isAuthPath && isValidSession) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
async function validateSession(token: string): Promise<boolean> {
// Call auth API endpoint or decode JWT
// Edge-compatible: use fetch, not Node.js crypto
try {
const response = await fetch('https://api.example.com/validate', {
headers: { Authorization: `Bearer ${token}` },
})
return response.ok
} catch {
return false
}
}callbackUrl 매개변수는 원래 목적지를 유지하여 로그인 후 사용자가 처음 요청한 페이지로 이동할 수 있게 합니다.
경로 매칭과 Matcher 설정
matcher 설정은 어떤 요청이 Middleware를 트리거하는지 결정합니다. 이를 올바르게 설정하지 않으면 성능 문제(모든 정적 자산에서 Middleware 실행) 또는 보안 허점(보호된 경로에서 인증 확인이 트리거되지 않음)이 발생합니다.
Next.js 16은 세 가지 matcher 구문을 지원합니다: 문자열 경로, 매개변수가 있는 경로 패턴, 부정 전방탐색이 있는 정규식과 유사한 패턴입니다.
// Option 1: Simple string paths
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}
// Option 2: Exclude static assets with negative lookahead
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|public/).*)'],
}
// Option 3: Conditional matching with has/missing
export const config = {
matcher: [
{
source: '/api/:path*',
has: [{ type: 'header', key: 'Authorization' }],
},
{
source: '/dashboard/:path*',
missing: [{ type: 'cookie', key: 'session' }],
},
],
}옵션 3의 has와 missing 조건은 Middleware가 실행되기 전에 로직을 추가합니다. 이는 Middleware 함수 내에서 확인하는 것보다 효율적입니다.
React / Next.js 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Middleware에서 요청과 응답 조작
인증 외에도 Middleware는 라우트 핸들러에 도달하기 전의 요청과 클라이언트에 도달하기 전의 응답을 수정할 수 있습니다. 일반적인 사용 사례로는 보안 헤더 추가, A/B 테스트를 위한 URL 재작성, 위치 정보 데이터 주입이 있습니다.
NextResponse 클래스는 각 수정 유형에 대한 메서드를 제공합니다. Rewrite는 브라우저 URL을 변경하지 않고 목적지를 변경하고, Redirect는 둘 다 업데이트합니다.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Add security headers to all responses
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set(
'Referrer-Policy',
'strict-origin-when-cross-origin'
)
// Inject geolocation into request headers for route handlers
const geo = request.geo
if (geo?.country) {
response.headers.set('x-user-country', geo.country)
response.headers.set('x-user-city', geo.city ?? 'unknown')
}
return response
}Geolocation 데이터(request.geo)는 Vercel의 Edge Network에서 설정됩니다. 자체 호스팅 배포에서는 호스팅 제공자를 통해 설정하거나 서드파티 IP 위치 정보 서비스를 사용해야 합니다.
A/B 테스트와 Feature Flag를 위한 URL Rewriting
Middleware Rewrite를 사용하면 클라이언트에게 알리지 않고 쿠키, 헤더 또는 무작위 할당에 따라 다른 페이지를 제공할 수 있습니다. 이 패턴은 A/B 테스트 프레임워크와 점진적 기능 롤아웃을 지원합니다.
Rewrite는 Edge에서 발생하므로 두 변형 모두 동일한 URL을 가지며 적절한 경우 Edge 캐싱의 이점을 얻습니다.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// Check for existing variant assignment
let variant = request.cookies.get('ab-variant')?.value
// Assign variant if not set (50/50 split)
if (!variant && pathname === '/pricing') {
variant = Math.random() < 0.5 ? 'control' : 'treatment'
const response = NextResponse.rewrite(
new URL(`/pricing/${variant}`, request.url)
)
response.cookies.set('ab-variant', variant, {
maxAge: 60 * 60 * 24 * 30, // 30 days
httpOnly: true,
})
return response
}
// Rewrite to assigned variant
if (variant && pathname === '/pricing') {
return NextResponse.rewrite(
new URL(`/pricing/${variant}`, request.url)
)
}
return NextResponse.next()
}쿠키는 변형 할당을 유지하여 사용자가 재방문 시 동일한 버전을 볼 수 있도록 보장합니다. 30일 만료는 실험 일관성과 새 실험에 사용자를 재할당하는 능력 사이의 균형을 유지합니다.
Edge Runtime 제한 사항과 해결 방법
Edge Runtime은 Node.js 호환성을 포기하고 글로벌 배포와 빠른 콜드 스타트를 제공합니다. Middleware는 bcrypt, jsonwebtoken(RS256 사용 시), TCP 소켓에 의존하는 데이터베이스 클라이언트와 같은 Node.js 전용 모듈을 가져올 수 없습니다.
Next.js Edge Runtime 문서에 지원되는 API 목록이 있습니다. Web Crypto, fetch 및 대부분의 Web Platform API가 작동합니다. 무거운 연산은 Node.js에서 실행되는 API 라우트로 이동해야 합니다.
import { jwtVerify } from 'jose' // Edge-compatible JWT library
import type { NextRequest } from 'next/server'
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
if (!token) {
return redirectToLogin(request)
}
try {
// jose library works in Edge Runtime
const { payload } = await jwtVerify(token, JWT_SECRET)
// Token is valid, check expiration
if (payload.exp && payload.exp < Date.now() / 1000) {
return redirectToLogin(request)
}
return NextResponse.next()
} catch {
// Invalid token signature or format
return redirectToLogin(request)
}
}
function redirectToLogin(request: NextRequest) {
const url = new URL('/login', request.url)
url.searchParams.set('callbackUrl', request.nextUrl.pathname)
return NextResponse.redirect(url)
}jose 라이브러리는 Edge 호환 JWT 작업을 제공합니다. Node.js의 crypto 모듈을 피하고 내부적으로 Web Crypto를 사용합니다.
Next.js Middleware 면접에서 자주 묻는 질문
면접관은 요청 라이프사이클, 보안 패턴, Edge 컴퓨팅 제약에 대한 이해를 평가하기 위해 Middleware에 대해 질문합니다. 이러한 질문은 Next.js를 사용하는 시니어 프론트엔드 및 풀스택 역할에서 출제됩니다.
Middleware는 요청 라이프사이클의 어디에서 실행됩니까?
Middleware는 요청이 Next.js 서버에 도달한 후 라우트 매칭이나 페이지 렌더링 전에 실행됩니다. Vercel에서는 사용자에게 가장 가까운 데이터 센터인 Edge에서 실행됩니다. 이 배치로 인해 유효하지 않은 요청이 원본 서버에 도달하지 않으므로 인증과 리다이렉트에 이상적입니다.
Middleware가 API 라우트와 비교하여 할 수 없는 것은 무엇입니까?
Middleware는 Node.js가 아닌 Edge Runtime에서 실행됩니다. Node.js 내장 모듈(fs, path, 특정 알고리즘을 사용하는 crypto), TCP 기반 데이터베이스 클라이언트, Node.js API에 의존하는 npm 패키지를 사용할 수 없습니다. 무거운 연산은 API 라우트 또는 Server Components에서 수행해야 합니다.
블로킹 없이 Middleware에서 인증을 처리하는 방법은 무엇입니까?
패턴은 세션 쿠키 확인, 인증 엔드포인트 검증 또는 JWT 서명 확인, 유효하지 않은 경우 리다이렉트로 구성됩니다. 모든 요청을 블로킹하지 않으려면 matcher 설정을 사용하여 보호된 경로에서만 Middleware를 실행합니다. 상태 비저장 JWT 검증은 모든 요청에서 외부 인증 서비스를 호출하는 것보다 빠릅니다.
Middleware에서 오류가 발생하면 어떻게 됩니까?
Middleware에서 처리되지 않은 오류는 클라이언트에 500 응답을 반환합니다. 페이지는 렌더링되지 않습니다. Middleware 로직을 try-catch로 감싸고 폴백 응답(주로 NextResponse.next())을 반환하면 완전한 요청 실패를 방지합니다. 계속하기 전에 오류를 로깅하면 디버깅에 도움이 됩니다.
이러한 개념에 대한 더 깊은 연습을 위해 Next.js Middleware와 인증 면접 문제 모듈에서 추가 시나리오를 다룹니다.
개발 및 프로덕션 환경에서 Middleware 디버깅
Middleware 버그는 코드가 브라우저 DevTools가 아닌 Edge에서 실행되기 때문에 까다롭습니다. 개발 모드에서는 Middleware 로그가 터미널에 표시되지만 프로덕션에서는 적절한 로깅 인프라가 필요합니다.
다음 접근 방식은 민감한 데이터를 노출하지 않으면서 문제를 디버깅하기에 충분한 컨텍스트로 Middleware 실행을 로깅합니다.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const start = Date.now()
const requestId = crypto.randomUUID()
// Log request start
console.log(JSON.stringify({
type: 'middleware_request',
requestId,
method: request.method,
path: request.nextUrl.pathname,
userAgent: request.headers.get('user-agent')?.slice(0, 100),
country: request.geo?.country,
}))
// Process request
const response = processRequest(request)
// Log completion
console.log(JSON.stringify({
type: 'middleware_response',
requestId,
duration: Date.now() - start,
status: response.status,
}))
// Pass request ID to route handlers
response.headers.set('x-request-id', requestId)
return response
}
function processRequest(request: NextRequest): NextResponse {
// Middleware logic here
return NextResponse.next()
}Vercel에서는 이러한 로그가 Functions 탭에 표시됩니다. 자체 호스팅 배포에서는 Datadog과 같은 서비스로 로그를 전송하거나 호스팅 플랫폼이 수집할 수 있는 구조화된 로깅을 사용해야 합니다.
Middleware 성능 모범 사례
Middleware는 매칭된 모든 요청에서 실행됩니다. 느린 Middleware 함수는 모든 페이지 로드에 지연 시간을 추가합니다. 목표는 대부분의 요청에서 5ms 미만으로 Middleware 실행을 완료하는 것입니다.
- 비동기 작업 최소화: 각
await는 지연 시간을 추가합니다. 가능한 경우 검증 결과를 캐시합니다. - matcher 설정 사용: Middleware 로직이 필요 없는 정적 자산과 공개 경로를 제외합니다.
- 모든 요청에서 외부 호출 피하기: 인증 서비스를 호출하는 대신 JWT를 로컬에서 검증합니다. 적절한 경우 쿠키에 결과를 캐시합니다.
- 번들 크기 작게 유지: Middleware에는 자체 번들 크기 제한이 있습니다(Vercel에서 1MB). 필요한 것만 가져옵니다.
React Next.js 기술 페이지에서는 Middleware 지식을 보완하는 Server Components와 데이터 페칭 패턴 등 관련 개념을 다룹니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
프로덕션을 위한 Next.js 16 Middleware 체크리스트
- Middleware는
app/이나pages/내부가 아닌 프로젝트 루트에middleware.ts로 배치 matcher를 설정하여_next/static,_next/image, 공개 자산을 Middleware 실행에서 제외- JWT 검증에는
jsonwebtoken대신jose와 같은 Edge 호환 라이브러리 사용 - 로그인으로 리다이렉트할 때 사용자가 원래 목적지로 돌아갈 수 있도록 쿼리 매개변수에 콜백 URL 저장
- 모든 라우트에 일관된 적용을 위해 Middleware에서 보안 헤더 추가
- 시스템 전체에서 요청을 추적하기 위해 요청 ID로 Middleware 실행 로깅
- 토큰을 로컬에서 검증하고 서명된 쿠키에 사용자 데이터를 캐시하여 외부 API 호출 최소화
React / Next.js 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 9월 20일 업데이트
태그
공유
관련 기사

Zustand 면접 질문 2026: React 상태 관리와 베스트 프랙티스 완벽 가이드
2026년 프론트엔드 면접에서 자주 출제되는 Zustand 관련 질문을 종합적으로 다룹니다. Zustand vs Context API, 미들웨어, TypeScript 통합, 테스트 패턴까지 실무 코드 예제와 함께 학습할 수 있습니다.

React 19 Suspense와 Concurrent Rendering 완벽 가이드: Streaming SSR과 면접 대비 2026
React 19의 Suspense, Concurrent Rendering, Streaming SSR의 동작 원리를 상세히 설명합니다. 2026년 프론트엔드 면접에서 자주 나오는 질문과 답변 예시도 포함되어 있습니다.

2026년 React 테스트: Vitest, React Testing Library와 모범 사례
2026년 최신 React 테스트 방법론을 상세히 설명합니다. Vitest, React Testing Library 활용법, 컴포넌트 테스트, 통합 테스트, 기술 면접 대비까지 종합적으로 다룹니다.