Next.js 사이트에서 모든 페이지가 홈으로 canonical 처리된 문제를 찾은 방법
핵심 요약
- 목록과 상세 페이지의 canonical이 홈을 가리키는지 실제 HTML에서 확인한다.
- 사이트맵에는 방문 가능한 공개 URL만 싣는다.
- 빌드 성공과 검색엔진이 읽는 HTML의 정확성은 별도로 확인한다.
이 글은 A2Z Studio 사이트를 점검하며 확인한 사례입니다. 개발 블로그 목록은 /blog, 프로젝트 상세는 /projects/[id]로 운영하고 있습니다. 그런데 배포된 /blog의 HTML을 확인하니 canonical URL이 /blog가 아니라 사이트 홈을 가리키고 있었습니다.
<link rel="canonical" href="https://a2z-studio.com" />
canonical은 검색엔진에 선호 URL을 알려주는 힌트입니다. 블로그 목록과 홈이 서로 다른 콘텐츠인데 같은 URL을 선호 주소로 표시하면, 목록 페이지를 독립적인 문서로 이해하는 데 방해가 됩니다. 빌드가 성공해도 놓치기 쉬운 오류였습니다.
원인: 루트 레이아웃의 공통 메타데이터
App Router의 app/layout.tsx에 모든 페이지가 상속받는 메타데이터가 있었습니다. 그 안의 alternates.canonical: '/'가 홈에는 맞지만 자식 페이지에까지 적용됐습니다. 페이지별 제목과 설명은 설정해 두었지만 canonical은 덮어쓰지 않았습니다.
확인은 렌더링된 HTML에서 하는 편이 빠릅니다.
curl -sSL https://a2z-studio.com/blog | grep -o 'rel="canonical"[^>]*'
curl -sSL https://a2z-studio.com/projects/proteinmeter | grep -o 'rel="canonical"[^>]*'
개발자 도구의 Elements 화면이나 curl 결과에서 각 URL이 자기 자신을 가리키는지 비교합니다. 단순히 소스 파일에 canonical이라는 문자열이 있는지만 찾으면 상속 결과를 놓칠 수 있습니다.
수정 기준: 공통값이 정말 공통인지 먼저 보기
홈 주소를 루트 레이아웃에 공통 canonical로 두는 대신, 경로가 확실한 페이지에 각각 지정했습니다. 목록은 /blog, 글 상세는 /blog/글-id, 프로젝트 상세는 /projects/프로젝트-id를 사용합니다. 상세 페이지는 generateMetadata가 이미 ID를 알고 있으므로 그 자리에서 주소를 만들 수 있습니다.
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${post.id}` },
};
같은 점검에서 사이트맵도 살폈습니다. 공개 프로젝트 목록은 설정 파일에서 true로 표시한 항목만 보여주는데, 사이트맵 생성기는 원본 데이터의 모든 프로젝트 ID를 넣고 있었습니다. 독자가 목록에서 찾을 수 없는 페이지를 검색엔진에 제출하는 셈입니다. 사이트맵은 화면에 공개하는 기준과 같은 데이터 소스를 사용하도록 맞춰야 합니다.
배포 후 확인할 세 가지
| 주소 | 확인할 것 |
|---|---|
/blog | canonical이 /blog를 가리키는가 |
/blog/글-id | 제목·설명·canonical이 해당 글 기준인가 |
/sitemap.xml | 글과 공개 프로젝트만 포함하는가 |
마지막으로 각 주소가 익명 방문에서 200 응답을 내는지도 확인합니다. 사이트맵에 URL이 있다는 사실만으로 접근 가능함을 증명할 수는 없습니다. 이 글의 범위는 메타데이터와 사이트맵의 정합성입니다. 색인 여부나 검색 순위는 Search Console에서 별도로 확인해야 하며, canonical을 고쳤다는 이유만으로 색인을 보장할 수 없습니다.
A2Z Studio가 만드는 앱과 웹 서비스는 프로젝트 목록에서 볼 수 있습니다.