한국어

수신 요청을 가로채고 수정하는 강력한 기능인 Next.js 미들웨어를 살펴보세요. 실제 예제를 통해 인증, 인가, 리디렉션, A/B 테스트 구현 방법을 배워보세요.

Next.js 미들웨어: 동적 애플리케이션을 위한 요청 가로채기 마스터하기

Next.js 미들웨어는 라우트에 도달하기 전에 수신 요청을 가로채고 수정할 수 있는 유연하고 강력한 방법을 제공합니다. 이 기능을 통해 인증 및 인가부터 리디렉션 및 A/B 테스트에 이르기까지 광범위한 기능을 구현하는 동시에 성능을 최적화할 수 있습니다. 이 종합 가이드는 Next.js 미들웨어의 핵심 개념을 안내하고 이를 효과적으로 활용하는 방법을 보여줍니다.

Next.js 미들웨어란 무엇인가?

Next.js의 미들웨어는 요청이 완료되기 전에 실행되는 함수입니다. 이를 통해 다음을 수행할 수 있습니다:

미들웨어 함수는 프로젝트의 루트에 있는 middleware.ts(또는 middleware.js) 파일에 정의됩니다. 이 함수는 애플리케이션 내의 모든 라우트 또는 구성 가능한 매처(matcher)를 기반으로 특정 라우트에 대해 실행됩니다.

주요 개념 및 이점

Request 객체

request 객체는 다음에 대한 정보를 포함하여 수신 요청에 대한 정보에 접근할 수 있게 해줍니다:

Response 객체

미들웨어 함수는 요청 결과를 제어하기 위해 Response 객체를 반환합니다. 다음 응답을 사용할 수 있습니다:

매처(Matchers)

매처를 사용하면 미들웨어를 적용할 라우트를 지정할 수 있습니다. 정규식이나 경로 패턴을 사용하여 매처를 정의할 수 있습니다. 이를 통해 미들웨어가 필요할 때만 실행되도록 하여 성능을 향상시키고 오버헤드를 줄일 수 있습니다.

엣지 런타임(Edge Runtime)

Next.js 미들웨어는 사용자와 가까운 곳에 배포될 수 있는 경량 자바스크립트 런타임 환경인 엣지 런타임에서 실행됩니다. 이러한 근접성은 대기 시간을 최소화하고 특히 전 세계적으로 분산된 사용자를 위한 애플리케이션의 전반적인 성능을 향상시킵니다. 엣지 런타임은 Vercel의 엣지 네트워크 및 기타 호환 플랫폼에서 사용할 수 있습니다. 엣지 런타임에는 몇 가지 제한 사항, 특히 Node.js API 사용에 대한 제한이 있습니다.

실용적인 예제: 미들웨어 기능 구현하기

1. 인증(Authentication)

인증 미들웨어는 사용자가 로그인해야 하는 라우트를 보호하는 데 사용할 수 있습니다. 다음은 쿠키를 사용하여 인증을 구현하는 방법의 예입니다:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 return NextResponse.next()
}

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

이 미들웨어는 auth_token 쿠키의 존재 여부를 확인합니다. 쿠키가 없으면 사용자는 /login 페이지로 리디렉션됩니다. config.matcher는 이 미들웨어가 /dashboard 아래의 라우트에 대해서만 실행되도록 지정합니다.

글로벌 관점: 다양한 지역의 사용자를 수용하기 위해 다양한 인증 방법(예: OAuth, JWT)을 지원하고 여러 ID 공급자(예: Google, Facebook, Azure AD)와 통합하도록 인증 로직을 조정하세요.

2. 인가(Authorization)

인가 미들웨어는 사용자 역할이나 권한에 따라 리소스에 대한 접근을 제어하는 데 사용할 수 있습니다. 예를 들어, 특정 사용자만 접근할 수 있는 관리자 대시보드가 있을 수 있습니다.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // 예시: API에서 사용자 역할 가져오기 (실제 로직으로 교체하세요)
 const userResponse = await fetch('https://api.example.com/userinfo', {
 headers: {
 Authorization: `Bearer ${token}`,
 },
 });
 const userData = await userResponse.json();

 if (userData.role !== 'admin') {
 return NextResponse.redirect(new URL('/unauthorized', request.url))
 }

 return NextResponse.next()
}

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

이 미들웨어는 사용자의 역할을 검색하고 admin 역할이 있는지 확인합니다. 그렇지 않으면 /unauthorized 페이지로 리디렉션됩니다. 이 예제는 플레이스홀더 API 엔드포인트를 사용합니다. `https://api.example.com/userinfo`를 실제 인증 서버 엔드포인트로 바꾸세요.

글로벌 관점: 사용자 데이터를 처리할 때 데이터 개인 정보 보호 규정(예: GDPR, CCPA)을 유념하세요. 민감한 정보를 보호하고 현지 법률을 준수하기 위해 적절한 보안 조치를 구현하세요.

3. 리디렉션(Redirection)

리디렉션 미들웨어는 사용자의 위치, 언어 또는 기타 기준에 따라 사용자를 리디렉션하는 데 사용할 수 있습니다. 예를 들어, IP 주소를 기반으로 사용자를 웹사이트의 현지화된 버전으로 리디렉션할 수 있습니다.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const country = request.geo?.country || 'US'; // 지리적 위치 확인 실패 시 미국으로 기본 설정

 if (country === 'DE') {
 return NextResponse.redirect(new URL('/de', request.url))
 }

 if (country === 'FR') {
 return NextResponse.redirect(new URL('/fr', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/'],
}

이 미들웨어는 사용자의 IP 주소를 기반으로 국가를 확인하고 웹사이트의 적절한 현지화 버전(독일의 경우 /de, 프랑스의 경우 /fr)으로 리디렉션합니다. 지리적 위치 확인에 실패하면 미국 버전으로 기본 설정됩니다. 이 기능은 geo 속성을 사용할 수 있을 때(예: Vercel에 배포된 경우)에만 의존한다는 점에 유의하세요.

글로벌 관점: 웹사이트가 여러 언어와 통화를 지원하는지 확인하세요. 사용자에게 선호하는 언어 또는 지역을 수동으로 선택할 수 있는 옵션을 제공하세요. 각 로케일에 맞는 날짜 및 시간 형식을 사용하세요.

4. A/B 테스트(A/B Testing)

미들웨어는 사용자를 페이지의 다른 변형에 무작위로 할당하고 그들의 행동을 추적하여 A/B 테스트를 구현하는 데 사용할 수 있습니다. 다음은 간단한 예제입니다:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

function getRandomVariant() {
 return Math.random() < 0.5 ? 'A' : 'B';
}

export function middleware(request: NextRequest) {
 let variant = request.cookies.get('variant')?.value;

 if (!variant) {
 variant = getRandomVariant();
 const response = NextResponse.next();
 response.cookies.set('variant', variant);
 return response;
 }

 if (variant === 'B') {
 return NextResponse.rewrite(new URL('/variant-b', request.url));
 }

 return NextResponse.next();
}

export const config = {
 matcher: ['/'],
}

이 미들웨어는 사용자를 'A' 또는 'B' 변형 중 하나에 할당합니다. 사용자에게 아직 variant 쿠키가 없으면 무작위로 하나가 할당되고 설정됩니다. 'B' 변형에 할당된 사용자는 /variant-b 페이지로 재작성됩니다. 그런 다음 각 변형의 성능을 추적하여 어느 것이 더 효과적인지 결정하게 됩니다.

글로벌 관점: A/B 테스트를 설계할 때 문화적 차이를 고려하세요. 한 지역에서 잘 작동하는 것이 다른 지역의 사용자에게는 공감을 얻지 못할 수 있습니다. A/B 테스트 플랫폼이 여러 지역의 개인 정보 보호 규정을 준수하는지 확인하세요.

5. 기능 플래그(Feature Flags)

기능 플래그를 사용하면 새 코드를 배포하지 않고도 애플리케이션의 기능을 활성화하거나 비활성화할 수 있습니다. 미들웨어는 사용자 ID, 위치 또는 기타 기준에 따라 사용자가 특정 기능에 접근할 수 있는지 여부를 결정하는 데 사용할 수 있습니다.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 // 예시: API에서 기능 플래그 가져오기
 const featureFlagsResponse = await fetch('https://api.example.com/featureflags', {
 headers: {
 'X-User-Id': 'user123',
 },
 });
 const featureFlags = await featureFlagsResponse.json();

 if (featureFlags.new_feature_enabled) {
 // 새 기능 활성화
 return NextResponse.next();
 } else {
 // 새 기능 비활성화 (예: 대체 페이지로 리디렉션)
 return NextResponse.redirect(new URL('/alternative-page', request.url));
 }
}

export const config = {
 matcher: ['/new-feature'],
}

이 미들웨어는 API에서 기능 플래그를 가져와서 new_feature_enabled 플래그가 설정되어 있는지 확인합니다. 설정되어 있으면 사용자는 /new-feature 페이지에 접근할 수 있습니다. 그렇지 않으면 /alternative-page로 리디렉션됩니다.

글로벌 관점: 기능 플래그를 사용하여 여러 지역의 사용자에게 점진적으로 새로운 기능을 출시하세요. 이를 통해 더 넓은 대상에게 기능을 출시하기 전에 성능을 모니터링하고 문제를 해결할 수 있습니다. 또한 기능 플래그 시스템이 전 세계적으로 확장되고 사용자 위치에 관계없이 일관된 결과를 제공하는지 확인하세요. 기능 출시에 대한 지역별 규제 제약을 고려하세요.

고급 기술

미들웨어 체이닝(Chaining)

여러 미들웨어 함수를 함께 연결하여 요청에 대한 일련의 작업을 수행할 수 있습니다. 이는 복잡한 로직을 더 작고 관리하기 쉬운 모듈로 분해하는 데 유용할 수 있습니다.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const response = NextResponse.next();

 // 첫 번째 미들웨어 함수
 const token = request.cookies.get('auth_token');
 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // 두 번째 미들웨어 함수
 response.headers.set('x-middleware-custom', 'value');

 return response;
}

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

이 예제는 하나의 미들웨어에 두 가지 작업을 보여줍니다. 첫 번째는 인증을 수행하고 두 번째는 사용자 정의 헤더를 설정합니다.

환경 변수 사용하기

API 키 및 데이터베이스 자격 증명과 같은 민감한 정보는 미들웨어 함수에 하드코딩하는 대신 환경 변수에 저장하세요. 이는 보안을 향상시키고 애플리케이션의 구성을 더 쉽게 관리할 수 있게 합니다.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const API_KEY = process.env.API_KEY;

export async function middleware(request: NextRequest) {
 const response = await fetch('https://api.example.com/data', {
 headers: {
 'X-API-Key': API_KEY,
 },
 });

 // ...
}

export const config = {
 matcher: ['/data'],
}

이 예제에서는 API_KEY를 환경 변수에서 가져옵니다.

오류 처리

미들웨어 함수에 강력한 오류 처리 기능을 구현하여 예기치 않은 오류로 인해 애플리케이션이 중단되는 것을 방지하세요. try...catch 블록을 사용하여 예외를 잡고 오류를 적절하게 기록하세요.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 try {
 const response = await fetch('https://api.example.com/data');
 // ...
 } catch (error) {
 console.error('Error fetching data:', error);
 return NextResponse.error(); // 또는 오류 페이지로 리디렉션
 }
}

export const config = {
 matcher: ['/data'],
}

모범 사례

일반적인 문제 해결

결론

Next.js 미들웨어는 동적이고 개인화된 웹 애플리케이션을 구축하기 위한 강력한 도구입니다. 요청 가로채기를 마스터함으로써 인증 및 인가부터 리디렉션 및 A/B 테스트에 이르기까지 광범위한 기능을 구현할 수 있습니다. 이 가이드에 설명된 모범 사례를 따르면 Next.js 미들웨어를 활용하여 글로벌 사용자 기반의 요구를 충족하는 고성능의 안전하고 확장 가능한 애플리케이션을 만들 수 있습니다. 미들웨어의 힘을 받아들여 Next.js 프로젝트에서 새로운 가능성을 열고 탁월한 사용자 경험을 제공하세요.