next.config.js redirects 설정으로 URL 리다이렉션 처리하기
Next.js에서 redirects 설정을 활용하여 SEO 친화적인 URL 리다이렉션과 마이그레이션 경로를 관리하는 방법을 알아봅니다.
next.config.js redirects 설정으로 URL 리다이렉션 처리하기
Introduction
URL 리다이렉션은 SEO 유지와 사용자 경험에 필수적입니다. Next.js의 redirects 설정을 사용하면 서버 수준에서 301/302 리다이렉션을 처리할 수 있습니다. 이 글에서는 다양한 리다이렉션 패턴과 실전 활용법을 다룹니다.
Environment
# 프로젝트 구조
my-app/
├── next.config.js
├── pages/
│ └── blog/
│ └── [...slug].js
├── lib/
│ └── redirects.ts
└── package.json
# 기술 스택
Next.js: 14.2.5
Node.js: 18.17.0Problem
URL 변경 시 기존 링크가 깨지고 SEO 점수가 하락합니다:
# 기존 URL: /blog/my-post
# 변경 후 URL: /articles/my-post
# 문제점:
# 1. 기존 북마크 접근 불가
# 2. 검색 엔진 색인 URL 깨짐
# 3. 외부 링크 깨짐Analysis
Next.js redirects 특징:
- 서버 수준 처리: 클라이언트 도달 전에 리다이렉션
- 정적 분석 가능: 빌드 시점에 리다이렉션 규칙 검증
- 조건부 리다이렉션: 헤더, 쿼리 파라미터 기반
- 성능 영향 최소화: 미들웨어보다 빠른 처리
Solution
1. 기본 리다이렉션 설정
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// 기본 리다이렉션
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true, // 301 리다이렉션
},
// 임시 리다이렉션
{
source: '/maintenance',
destination: '/sorry',
permanent: false, // 302 리다이렉션
},
// 여러 경로 리다이렉션
{
source: '/docs/:path*',
destination: '/documentation/:path*',
permanent: true,
},
];
},
};
module.exports = nextConfig;2. 조건부 리다이렉션
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// 헤더 기반 리다이렉션
{
source: '/api/legacy/:path*',
destination: '/api/v2/:path*',
permanent: true,
has: [
{
type: 'header',
key: 'x-api-version',
value: 'v1',
},
],
},
// 쿼리 파라미터 기반 리다이렉션
{
source: '/search',
destination: '/results',
permanent: false,
has: [
{
type: 'query',
key: 'q',
value: '(.*)',
},
],
},
// 쿠키 기반 리다이렉션
{
source: '/dashboard',
destination: '/login',
permanent: false,
has: [
{
type: 'cookie',
key: 'session',
value: '',
// 쿠키가 없으면 리다이렉션
},
],
},
// 호스트 기반 리다이렉션
{
source: '/:path*',
destination: 'https://www.example.com/:path*',
permanent: true,
has: [
{
type: 'host',
value: 'example.com',
},
],
},
];
},
};
module.exports = nextConfig;3. 동적 리다이렉션 데이터베이스
// lib/redirects.ts
import { Redirect } from 'next/dist/lib/load-custom-routes';
interface RedirectRule {
source: string;
destination: string;
permanent: boolean;
conditions?: {
headers?: Record;
query?: Record;
cookies?: Record;
};
}
// 데이터베이스에서 리다이렉션 규칙 가져오기
export async function getRedirects(): Promise {
// 실제 환경에서는 DB나 CMS에서 가져옴
const rules: RedirectRule[] = [
{
source: '/blog/:slug',
destination: '/articles/:slug',
permanent: true,
},
{
source: '/old-path/:path*',
destination: '/new-path/:path*',
permanent: true,
},
];
return rules.map(rule => ({
source: rule.source,
destination: rule.destination,
permanent: rule.permanent,
}));
} // next.config.js
const { getRedirects } = require('./lib/redirects');
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
// 빌드 시점에 리다이렉션 규칙 로드
const dbRedirects = await getRedirects();
// 하드코딩된 규칙과 DB 규칙 병합
const staticRedirects = [
{
source: '/home',
destination: '/',
permanent: true,
},
];
return [...staticRedirects, ...dbRedirects];
},
};
module.exports = nextConfig;4. 마이그레이션 리다이렉션 패턴
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// 섹션별 마이그레이션
{
source: '/products/:id',
destination: '/shop/items/:id',
permanent: true,
},
// 사용자 프로필 마이그레이션
{
source: '/user/:username',
destination: '/profiles/:username',
permanent: true,
},
// API 엔드포인트 마이그레이션
{
source: '/api/users/:id',
destination: '/api/v2/accounts/:id',
permanent: true,
},
// 카테고리 구조 변경
{
source: '/category/:category/:slug',
destination: '/categories/:category/posts/:slug',
permanent: true,
},
// 페이지네이션 마이그레이션
{
source: '/blog/page/:page',
destination: '/blog?page=:page',
permanent: false,
},
];
},
};
module.exports = nextConfig;5. A/B 테스팅 리다이렉션
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// A/B 테스팅: 50% 트래픽 리다이렉션
{
source: '/landing',
destination: '/landing-v2',
permanent: false,
has: [
{
type: 'cookie',
key: 'ab-test',
value: 'variant-b',
},
],
},
// 특정 헤더가 있는 경우만 리다이렉션
{
source: '/beta',
destination: '/new-feature',
permanent: false,
has: [
{
type: 'header',
key: 'x-beta-user',
value: 'true',
},
],
},
];
},
};
module.exports = nextConfig;6. 리다이렉션 모니터링
// lib/redirect-monitor.ts
interface RedirectLog {
timestamp: Date;
source: string;
destination: string;
permanent: boolean;
userAgent?: string;
ip?: string;
}
export class RedirectMonitor {
private logs: RedirectLog[] = [];
log(redirect: Omit) {
this.logs.push({
...redirect,
timestamp: new Date(),
});
}
getStats() {
const stats = this.logs.reduce((acc, log) => {
acc[log.source] = (acc[log.source] || 0) + 1;
return acc;
}, {} as Record);
return Object.entries(stats)
.sort(([, a], [, b]) => b - a)
.slice(0, 10);
}
}
export const redirectMonitor = new RedirectMonitor(); 7. 리다이렉션 테스트
// scripts/test-redirects.js
const redirects = require('../next.config.js').redirects;
async function testRedirects() {
const rules = await redirects();
for (const rule of rules) {
console.log(`${rule.source} → ${rule.destination} (${rule.permanent ? '301' : '302'})`);
}
console.log(`\n총 ${rules.length}개의 리다이렉션 규칙`);
}
testRedirects();# 리다이렉션 테스트 실행
node scripts/test-redirects.jsLessons Learned
- 301 vs 302 선택: 영구적 변경은 301, 임시적 변경은 302
- SEO 모니터링: 리다이렉션 후 검색 엔진 색인 상태 확인
- 성능 고려: 리다이렉션 체인 지양 (최대 1회)
- 로그 관리: 리다이렉션 사용량 모니터링으로 불필요한 규칙 제거
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.