탱구의 놀이터

Notes · 검색 노출

같은 글이 두 주소로 있을 때 무엇이 사라지나

2026-10-14

canonical 태그만으로는 부족합니다. 구글은 대체로 합쳐 주지만 네이버는 www 유무를 다른 사이트로 봅니다. 이 사이트가 실제로 그랬고, Cloudflare Pages에서 고치다 예상 못 한 데서 막혔습니다.

같은 글이 두 주소로 열리는 일은 흔합니다. www가 붙은 것과 안 붙은 것, http와 https. 보는 사람은 차이를 못 느끼지만 검색엔진은 다른 사이트로 셉니다. 이 사이트에서 실제로 그러고 있었고, 고치다가 예상 못 한 데서 막혔습니다.

한 줄로 먼저

canonical 태그만으로는 부족합니다. 구글은 대체로 합쳐 주지만 네이버는 그렇지 않습니다. 301로 한쪽으로 보내야 끝납니다.

우리 사이트가 그랬습니다

네이버가 수집을 못 하길래 점검하다 발견했습니다.

curl -o /dev/null -w "%{http_code}" https://www.taeyeun.com/
200        ← 넘어가지 않고 그대로 내준다

그냥 열리는 정도가 아니었습니다. 본문이 글자 하나까지 같았습니다.

apex 해시  4ded6949ed3b
www  해시  4ded6949ed3b
확인한 것apexwww
본문200200 (동일)
robots.txt200200
sitemap.xml200200
네이버 소유확인 메타있음있음

검색엔진 눈에는 똑같은 사이트가 두 개였습니다. 사이트맵까지 양쪽에서 열리니 어느 쪽을 믿어야 할지 알 수가 없습니다.

canonical은 왜 부족한가

양쪽 모두 대표 주소를 제대로 가리키고 있었습니다.

<link rel="canonical" href="https://taeyeun.com/">   ← www 쪽에도 이게 있었다

그런데도 문제가 됩니다. canonical은 명령이 아니라 힌트이기 때문입니다. 구글도 "참고는 하되 다른 신호와 함께 판단한다"는 입장이고, 네이버는 www 유무를 아예 다른 사이트로 다룹니다.

그래서 순서가 이렇습니다. 301 리다이렉트가 먼저고, canonical은 보조입니다. 반대로 알고 있는 경우가 많습니다.

무엇이 사라지나

갈라지는 것무슨 일이 생기나
링크누가 www로 걸고 누가 안 붙이면, 한 글의 평가가 둘로 쪼개진다
색인같은 내용이 두 번 수집된다. 크롤 예산을 두 배로 쓴다
검색 노출둘 중 어느 쪽이 뜰지 내가 못 고른다
서치어드바이저·서치콘솔등록한 쪽과 실제로 뜨는 쪽이 어긋나면 데이터가 안 보인다
사이트맵등록한 호스트와 사이트맵 안 주소가 다르면 통째로 거부될 수 있다

마지막 줄이 특히 위험합니다. 사이트맵의 주소가 전부 https://taeyeun.com/…인데 서치어드바이저에는 www.taeyeun.com으로 등록해 두었다면, 등록한 사이트 밖의 주소를 제출한 셈이 됩니다.

⚠️ 고치다 막힌 곳 — Cloudflare Pages

Cloudflare Pages에는 _redirects라는 파일이 있습니다. 호스트까지 적을 수 있습니다.

https://www.example.com/* https://example.com/:splat 301!

문법도 맞고 배포도 됐는데 동작하지 않았습니다. 확인해 보니 www가 그대로 200이었습니다.

Pages는 Function을 _redirects보다 먼저 봅니다. functions/[[path]].js처럼 모든 경로를 받는 함수가 있으면 요청이 거기서 끝나고 _redirects까지 내려오지 않습니다.

함수가 없는 순수 정적 사이트라면 _redirects로 됩니다. 함수가 있으면 그 함수 맨 앞에 넣어야 합니다.

export async function onRequest(context) {
  const { request, env, next } = context;

  const u = new URL(request.url);
  if (u.hostname.startsWith('www.')) {
    u.hostname = u.hostname.slice(4);
    return Response.redirect(u.toString(), 301);
  }

  if (!env.DB) return next();   // ← DB 검사보다 앞에 둔다
  …
}

DB를 보는 줄보다 위에 둡니다. 데이터베이스가 죽어도 리다이렉트는 살아 있어야 하기 때문입니다.

⚠️ 순서를 틀리면 소유확인이 깨집니다

이게 제일 조심할 곳입니다.

서치어드바이저·서치콘솔에 www로 등록해 두었다면, 리다이렉트를 먼저 걸면 그 등록의 소유확인이 깨집니다. www가 더 이상 페이지를 안 내주니 확인할 방법이 없어집니다.
  1. 남길 주소(대개 www 없는 쪽)를 먼저 등록하고 소유확인까지 끝낸다
  2. 그다음에 리다이렉트를 건다

점검하는 법

네 가지 주소를 전부 눌러 봅니다. 하나라도 200이면 거기가 중복입니다.

for u in "http://도메인/" "https://도메인/" \
         "http://www.도메인/" "https://www.도메인/"; do
  printf "%-30s " "$u"
  curl -s -o /dev/null -w "%{http_code} -> " "$u"
  curl -s -o /dev/null -L -w "%{url_effective}\n" "$u"
done

원하는 모양은 이렇습니다.

들어간 주소나와야 하는 결과
http://도메인/301 → https://도메인/
https://도메인/200 (대표 주소)
http://www.도메인/301 → https://도메인/
https://www.도메인/301 → https://도메인/

세 가지를 더 봅니다

  1. 홈만 보지 마세요. www/notes/, www/sitemap.xml처럼 하위 경로도 넘어가야 합니다
  2. 301인지 302인지 봅니다. 302는 "임시"라 평가가 넘어가지 않습니다
  3. 한 번에 가는지 봅니다. http+www → http → https처럼 두 번 거치면 그만큼 샙니다
고친 뒤에는 대표 주소가 멀쩡한지를 반드시 같이 확인합니다. 리다이렉트를 잘못 걸면 무한 루프에 빠져 사이트 전체가 안 열립니다. 저는 열두 개 주소가 전부 200인 것을 확인하고 끝냈습니다.

정리하면

  1. 네 가지 주소를 눌러 봅니다 — 하나라도 200이면 중복입니다
  2. canonical은 힌트일 뿐입니다. 네이버는 www 유무를 다른 사이트로 봅니다
  3. 301로 한쪽에 모읍니다. 302는 평가가 안 넘어갑니다
  4. 등록 먼저, 리다이렉트 나중 — 순서를 틀리면 소유확인이 깨집니다
  5. Cloudflare Pages는 Function이 _redirects를 가립니다 — catch-all 함수가 있으면 함수 안에 넣으세요
  6. 고친 뒤 대표 주소가 200인지 꼭 확인합니다

이어서 읽기

근거

  • 직접 확인 (2026-10-14) — www.taeyeun.com이 301 없이 200, apex와 본문 해시 동일(4ded6949ed3b), robots.txt·sitemap.xml·네이버 소유확인 메타가 양쪽에 모두 존재
  • 직접 확인 — Cloudflare Pages에서 _redirects의 호스트 규칙이 catch-all Function에 가려 동작하지 않음. 함수 맨 앞으로 옮겨 해결
  • 직접 확인 — 수정 후 www·http://www의 모든 경로가 301로 대표 주소로 이동, 대표 주소 12개 경로는 200 유지
  • Google 검색 센터 — 표준 URL 지정 문서. rel="canonical"은 여러 신호 중 하나로 다뤄짐
2026년 10월 14일에 이 사이트에서 직접 겪고 고친 내용입니다. 플랫폼마다 리다이렉트를 거는 자리가 다릅니다. 적용 전에 대표 주소가 살아 있는지를 반드시 확인하세요.