문제 상황
Google Search Console에서 "페이지 색인이 생성되지 않음: 중복 페이지, Google에서 사용자와 다른 표준을 선택함" 오류 발생
- 사용자가 원하는 표준 URL:
https://example.com/en/blog/...
- Google이 선택한 표준 URL:
https://www.example.com/posts/...
원인 분석
1. Canonical 태그 문제
문제점: 모든 페이지의 canonical 태그가 메인 페이지를 가리키고 있음
<!-- 잘못된 예시 -->
<link rel="canonical" href="https://example.com" />
원인: 각 페이지의 고유한 URL을 canonical로 지정하지 않아 Google이 혼란스러워함
2. 메타 태그 불일치 문제
문제점: 페이지마다 메타 태그 작성 방식이 달라 일관성 없음
<!-- 페이지 A -->
<meta property="og:url" content="https://example.com/posts/..." />
<!-- 페이지 B -->
<meta property="og:url" content="https://www.example.com/blog/..." />
<!-- 페이지 C -->
<meta property="og:url" content="https://example.com/en/articles/..." />
원인:
- 수동으로 작성한 메타 태그들이 서로 다른 URL 형식 사용
- OG 태그, Twitter 카드, 일반 메타 태그가 제각각 다른 정보 포함
3. 307 임시 리다이렉트 문제
문제점: 리다이렉트가 307 (Temporary Redirect)로 처리되고 있음
curl -IL https://www.example.com/posts/article-title
HTTP/2 307 # 임시 리다이렉트
location: /en/blog/article-title
원인:
- Next.js의
NextResponse.redirect()
는 기본적으로 307 상태 코드 사용 - 307은 임시 리다이렉트로, Google이 "나중에 원래 URL로 돌아갈 수 있다"고 판단
- 영구적인 URL 이동임을 Google에 알리지 못함
문제 해결
1. Canonical 태그 수정
Before:
<link rel="canonical" href="https://example.com" />
After:
<link rel="canonical" href="https://example.com/en/blog/solving-duplicate-content-issues" />
적용 방법:
- 각 페이지의 고유한 정규화된 URL을 canonical로 지정
- 동적으로 현재 페이지 URL을 삽입하도록 수정
2. 메타 태그 통합 및 표준화
Before: 페이지마다 다른 방식으로 메타 태그 작성
// 수동 작성
<meta property="og:title" content="..." />
<meta name="twitter:title" content="..." />
<title>...</title>
After: Next.js의 generateMetadata
API 사용
// app/[locale]/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const canonicalUrl = `https://example.com/en/blog/${params.slug}`
return {
title: 'Page Title',
description: 'Page Description',
alternates: {
canonical: canonicalUrl,
},
openGraph: {
url: canonicalUrl,
title: 'Page Title',
description: 'Page Description',
},
twitter: {
card: 'summary_large_image',
title: 'Page Title',
description: 'Page Description',
},
}
}
장점:
- 모든 메타 태그가 동일한 정보 사용
- 자동으로 정규화된 URL 생성
- 유지보수 용이
3. 301 영구 리다이렉트 적용
Before: middleware.ts
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname.startsWith('/posts/')) {
url.pathname = url.pathname.replace('/posts/', '/en/blog/')
return NextResponse.redirect(url) // 기본값 307
}
}
After: 301 상태 코드 명시
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname.startsWith('/posts/')) {
url.pathname = url.pathname.replace('/posts/', '/en/blog/')
return NextResponse.redirect(url, { status: 301 }) // 영구 리다이렉트
}
}
검증 : curl로 리다이렉트 확인
curl -IL https://example.com/blog
// HTTP/2 301 ... 으로 리다이렉트 확인