← 전체 글로 돌아가기

Next.js

Next.js에서 canonical URL 제대로 쓰기

SEO를 위해 canonical을 설정했는데 검색 엔진이 인식하지 못하는 문제. 빌드와 런타임에서 확인할 점들.

Canonical이 뭔지 다시 확인한다

Canonical URL은 "이 페이지의 원본은 이 주소입니다"라고 검색 엔진에 알려주는 메타 태그다.

<link rel="canonical" href="https://example.com/products/item" />

Next.js에서는 이게 여러 곳에서 정의될 수 있다. Metadata object, Head component, getHead 함수 등. 그리고 이들이 충돌하면 검색 엔진은 혼란스러워한다.

빌드 타임 확인

npm run build

빌드 로그에서 warning이나 error가 없는지 봐야 한다. 특히 next build가 dynamic metadata와 static metadata를 섞어 쓰면 경고를 출력한다.

# 빌드된 페이지의 메타 정보 확인
grep -r "canonical" .next/server/app

실제 HTML을 본다

가장 정확한 방법은 브라우저에서 직접 본다.

# 프로덕션 빌드 서버 실행
npm run build
npm run start

# 다른 터미널에서
curl -s http://localhost:3000/products/item | grep canonical

canonical 태그가 정확히 어디에 있는지, 어떤 값을 가지고 있는지 확인한다.

동적 경로에서의 canonical

[id] 같은 동적 세그먼트를 쓸 때는 context에 따라 canonical이 달라져야 한다.

// app/products/[id]/page.tsx
export async function generateMetadata(
  { params }: { params: { id: string } }
) {
  return {
    title: `Product ${params.id}`,
    alternates: {
      canonical: `https://example.com/products/${params.id}`,
    },
  };
}

주의할 점은 params가 항상 string이고, URL 인코딩 처리가 필요할 수 있다는 것이다.

Sitemap과 RSS에서의 일관성

Canonical URL이 sitemap에서는 다를 수 있다. 일관성을 확인해야 한다.

# Sitemap 생성 확인
curl -s http://localhost:3000/sitemap.xml | head -20

# RSS의 item link와 비교
curl -s http://localhost:3000/feed.xml | grep '<link>' | head -5

Sitemap의 URL과 페이지의 canonical이 일치해야 검색 엔진이 혼란스러워하지 않는다.

OG 태그도 함께 확인

Canonical과 함께 og:url도 일관성 있게 설정해야 한다.

curl -s http://localhost:3000 | grep -E "canonical|og:url"

둘이 다르면 소셜 미디어 공유 시 메타 정보가 꼬일 수 있다.

ISR이나 revalidate 설정

Incremental Static Regeneration을 쓸 때는 더 주의해야 한다.

export const revalidate = 3600; // 1시간마다 재생성

Revalidate 시간 동안은 캐시된 페이지가 나가는데, canonical이 그 동안 바뀌면 안 된다. 특히 URL 구조가 변경되는 마이그레이션 시에는 리다이렉트와 canonical을 함께 관리해야 한다.

최종 점검

배포 전에 몇 가지 URL로 직접 확인한다.

  1. 각 페이지의 canonical이 자신의 URL과 일치하는가
  2. 리다이렉트된 페이지의 canonical은 최종 URL을 가리키는가
  3. Sitemap의 모든 URL이 대응하는 페이지에서 canonical으로 언급되는가

이 세 가지가 모두 일치하면 검색 엔진 크롤러는 헷갈리지 않는다.