Next.js rewrites 설정으로 프록시 라우팅 구현하기
next.config.js에서 rewrites를 활용하여 백엔드 API 프록시와 마이그레이션 경로를 안전하게 처리하는 방법을 알아봅니다.
Next.js rewrites 설정으로 프록시 라우팅 구현하기
Introduction
Next.js의 rewrites 기능은 클라이언트에게 변경 없이 백엔드 경로를 재매핑하는 강력한 도구입니다. 특히 마이크로서비스 아키텍처에서 API 게이트웨이 패턴을 구현하거나, 외부 서비스와의 통합을 안전하게 관리할 때 유용합니다. 이 글에서는 실제 프로덕션 환경에서 rewrites를 활용하는 다양한 패턴을 다룹니다.
Environment
# 프로젝트 구조
my-app/
├── next.config.js
├── pages/
│ └── api/
│ └── proxy/
│ └── [...path].js
├── lib/
│ └── api-client.js
└── package.json
# 사용 기술 스택
Node.js: v18.17.0
Next.js: 14.2.5# 버전 확인
node -v
npm list nextProblem
외부 API를 호출할 때 CORS 정책이나 API 키 노출 문제가 발생합니다. 클라이언트에서 직접 외부 엔드포인트를 호출하면 보안상 위험합니다.
// ❌ 위험한 클라이언트 사이드 직접 호출
// lib/api-client.js
export const fetchFromExternalAPI = async (endpoint) => {
const response = await fetch(`https://api.external-service.com/${endpoint}`, {
headers: {
'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}` // 클라이언트에 노출됨!
}
});
return response.json();
};Analysis
Rewrites를 사용하면 서버 사이드에서 외부 API를 프록시하여 클라이언트에 실제 엔드포인트를 노출하지 않습니다.
// ✅ next.config.js rewrites 설정
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
// 기본 프록시 패턴
{
source: '/api/external/:path*',
destination: 'https://api.external-service.com/:path*',
},
// 조건부 프록시 (헤더 기반)
{
source: '/api/v2/:path*',
destination: 'https://api-v2.external-service.com/:path*',
has: [
{
type: 'header',
key: 'x-api-version',
value: 'v2',
},
],
},
// 쿼리 파라미터 기반 라우팅
{
source: '/search',
destination: 'https://search-api.external-service.com/query',
has: [
{
type: 'query',
key: 'provider',
value: 'external',
},
],
},
// beforeFiles rewrites (API 라우트 우선)
{
source: '/api/internal/:path*',
destination: '/api/internal/:path*',
},
// afterFiles rewrites (정적 파일 후)
{
source: '/legacy/:path*',
destination: '/new-path/:path*',
},
];
},
// rewrites 실행 순서 제어
async rewrites() {
return {
// beforeFiles: API 라우트와 동적 라우트보다 먼저 실행
beforeFiles: [
{
source: '/api/gateway/:path*',
destination: 'https://gateway.example.com/:path*',
},
],
// afterFiles: 정적 파일 이후 실행
afterFiles: [
{
source: '/docs/:path*',
destination: 'https://docs.example.com/:path*',
},
],
// fallback: 모든 매칭 실패 시 실행
fallback: [
{
source: '/:path*',
destination: 'https://fallback.example.com/:path*',
},
],
};
},
};
module.exports = nextConfig;// ✅ 안전한 서버 사이드 프록시 구현
// pages/api/proxy/[...path].js
export default async function handler(req, res) {
const { path } = req.query;
const targetUrl = `https://api.external-service.com/${path.join('/')}`;
// API 키를 서버에서만 사용
const headers = {
'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}`,
'Content-Type': 'application/json',
};
try {
const response = await fetch(targetUrl, {
method: req.method,
headers,
body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined,
});
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
console.error('Proxy error:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
}Solution
1. 환경 변수 관리
# .env.local
EXTERNAL_API_KEY=your-secret-key-here
EXTERNAL_API_SECRET=your-secret-here
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com2. Rewrite 커스텀 헤더 추가
// next.config.js
const nextConfig = {
async rewrites() {
return [
{
source: '/api/external/:path*',
destination: 'https://api.external-service.com/:path*',
// 커스텀 헤더 추가
headers: [
{
key: 'X-Custom-Header',
value: 'my-value',
},
{
key: 'X-Request-Source',
value: 'nextjs-proxy',
},
],
},
];
},
};3. 환경별 동적 설정
// next.config.js
const isProd = process.env.NODE_ENV === 'production';
const nextConfig = {
async rewrites() {
const baseRewrites = [
{
source: '/api/external/:path*',
destination: isProd
? 'https://api.production.example.com/:path*'
: 'https://api.staging.example.com/:path*',
},
];
// 개발 환경에서만 로컬 백엔드 연결
if (!isProd) {
baseRewrites.push({
source: '/api/local/:path*',
destination: 'http://localhost:8080/:path*',
});
}
return baseRewrites;
},
};Lessons Learned
Rewrites 설정 시 주의사항:
- 보안 고려사항: API 키는 절대 클라이언트에 노출되지 않도록 서버에서만 관리합니다
- 성능 영향: 불필요한 rewrite 규칙은 제거하여 라우팅 오버헤드를 줄입니다
- 모니터링: 프록시된 요청에 대한 로깅과 모니터링을 구현합니다
- 캐싱 전략: 재작성된 요청의 캐싱 동작을 테스트합니다
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.