비슷한 주제를 다루던 페이지 여러 개를 대표 페이지 몇 개로 합치면서, 예전 링크로 들어오는 사람들을 어떻게 처리할지 고민했던 적이 있어요. img-compressor 저장소의 next.config.js redirects()로 실제로 어떻게 정리했는지 보여드릴게요.
경로 매핑은 배열 말고 객체로 관리했어요
async redirects() {
const legacyRedirects = {
"naver-blog-image-size-guide": "blog-image-optimization-checklist",
"google-image-seo-alt-filename-guide": "jpg-png-webp-format-choice",
"product-thumbnail-compression-checklist": "ecommerce-product-image-compression-guide",
"image-quality-60-70-80-comparison": "image-compression-benchmark-results",
// ...
};
return Object.entries(legacyRedirects).map(([source, destination]) => ({
source: \`/:locale(ko|en)/\${source}\`,
destination: \`/:locale/\${destination}\`,
permanent: true,
}));
}
예전 경로랑 새 경로를 { 예전: 새로운 } 객체로 한 곳에 모아두고, Object.entries로 돌려서 Next.js가 원하는 { source, destination, permanent } 형태로 바꿔요. 배열로 하나씩 나열하는 대신 객체로 관리하니까, 어떤 예전 경로가 어디로 합쳐졌는지 한눈에 보이고 항목 추가할 때도 배열 구조를 반복해서 안 써도 되더라고요.
동적 세그먼트로 언어별 경로를 한 번에 처리했어요
/:locale(ko|en)/naver-blog-image-size-guide처럼 source에 정규식 들어간 동적 세그먼트를 쓰면, /ko/...랑 /en/...를 따로 등록 안 해도 규칙 하나로 같이 처리돼요. destination의 :locale도 매칭된 값을 그대로 받아서, 리다이렉트 후에도 보던 언어가 그대로 유지되고요.
permanent: true를 고른 이유
permanent: true는 HTTP 301로 응답한다는 뜻이에요. 페이지를 합쳐도 예전 URL로 들어오는 검색 유입이랑 외부 링크는 계속 있을 수 있어서, 302(임시)가 아니라 301로 응답해서 검색 엔진한테 "이 페이지 색인 신호는 새 URL로 옮겨가라"고 확실히 알려줬어요. 반대로 페이지 통합이 임시적이거나 나중에 되돌릴 수도 있다면 301은 안 쓰는 게 안전해요. 한 번 301로 알린 관계는 검색 엔진 캐시에도 오래 남더라고요.