레거시 경로 301 리다이렉트로 검색 유입 복구
목차
사이트 URL 체계를 레거시 제목 기반(예: /my-awesome-article)에서 ID 기반(/p/<id>)으로 마이그레이션했을 때, 301 리다이렉트 처리가 빠져 있던 걸 발견하고 복구했다. 간단해 보이지만 SEO와 사용자 경험 관점에서 무시할 수 없는 작업이었다.
URL 체계 마이그레이션과 레거시 지원
대부분의 서비스가 성장하면서 마주하는 문제가 URL 설계 변경이다. 초기에는 제목을 slug로 사용하는 방식이 직관적이고 사용자 친화적이다. 하지만 운영하다 보면 여러 제약을 만난다. 제목을 바꾸면 URL이 깨진다든지, 같은 제목의 중복 콘텐츠가 생기거나, 국제화할 때 다국어 경로를 모두 관리해야 한다는 문제들 말이다. 그래서 점진적으로 고정된 ID 기반 URL로 전환하는 게 일반적인 패턴이다.
Astro 같은 정적 사이트 생성기를 쓸 때는 [slug].astro 같은 동적 라우트 파일로 여러 경로를 한 컴포넌트에서 처리한다. 이런 구조에서 레거시 경로를 지원하려면 명시적으로 리다이렉트 로직을 추가해야 한다. 단순히 새 경로만 지원하면 기존 검색 엔진 인덱싱, 외부 링크, 북마크 같은 것들이 전부 깨진다.
301 리다이렉트가 중요한 이유
HTTP 301은 "영구 이동"을 의미한다. 302(임시 이동)과 다르게 브라우저와 검색 엔진 모두 이를 캐싱하고, SEO 평가도 새 경로로 이관한다. 검색 결과에서 오래된 링크로 들어온 사용자도 자동으로 새 경로로 이동되므로, 404 에러 페이지를 보는 상황을 완전히 피할 수 있다.
반대로 리다이렉트가 없으면:
- 검색 순위 하락: 이전 경로로의 가치가 인정되지 않음
- 분석 왜곡: 유입 트래픽이 쪼개짐
- 사용자 이탈: 깨진 링크로 들어온 사람들이 바로 나감
- 외부 링크 가치 손실: 다른 사이트에서 링크한 이전 경로가 죽음
이건 단순한 개발 작업이 아니라 비즈니스에 직결되는 부분이다.
구현: Astro [slug] 라우팅에서 리다이렉트 처리
Astro의 동적 라우트 파일에서는 보통 이렇게 처리할 수 있다:
---
// src/pages/[slug].astro
import { getCollection } from 'astro:content'
export async function getStaticPaths() {
const posts = await getCollection('posts')
return posts.map(post => ({
params: { slug: post.data.newSlug }, // 새 ID 기반 경로
props: { post }
}))
}
const { slug } = Astro.params
const { post } = Astro.props
// 레거시 경로로 들어온 경우 감지 후 리다이렉트
if (slug !== post.data.newSlug) {
return Astro.redirect(`/p/${post.data.id}`, 301)
}
---
핵심은 레거시 slug와 새로운 ID를 매핑하는 데이터를 유지하는 것이다. 이전 제목 기반 slug가 어느 ID로 매핑되는지 메타데이터에 저장해둬야 한다.
팀과 운영 관점에서의 교훈
이런 작업이 늦게 발견되는 이유는 보통 몇 가지다:
- 마이그레이션 계획 단계에서 간과: "새 경로 구현하기"에만 집중하고 "레거시 호환성"을 별도 작업으로 안 본다
- 테스트 부재: 실제 이전 경로로 접속해보는 시나리오가 없다
- 모니터링 부족: 404 에러율, 리다이렉트 체인 감지 없음
이걸 방지하려면:
- URL 변경이 있을 때 리다이렉트 매핑 확인을 코드 리뷰 체크리스트에 넣기
- 외부 링크, SEO 도구, 분석 서비스에서 실제 경로 변화를 모니터링하기
- 구형 경로 접속 시 리다이렉트 여부를 자동화된 E2E 테스트로 검증하기
지금 다시 보니 초기에 이 부분을 설계할 때 "언젠가 추가하자" 식으로 넘어갔던 것 같다. 작은 조정처럼 보이지만, 검색 유입과 직결된 부분이라 우선순위를 더 높게 가져갔어야 했다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.