태그 485개를 전부 색인시키고 있었습니다: 블로그 SEO를 다시 손본 기록
블로그에는 이미 canonical, sitemap, robots.txt, 글별 Open Graph 이미지, 구조화 데이터가 들어가 있었습니다. RSS와 Atom, JSON Feed도 있었고 관련 글 링크도 붙어 있었습니다. 그래서 처음에는 큰 구멍보다는 메타 설명을 조금 다듬는 정도를 예상했습니다.
실제 응답을 보니 아니었습니다. 눈에 잘 띄지 않는 목록 페이지 두 곳에서 검색엔진에 잘못된 신호를 주고 있었고, 태그를 많이 달수록 좋을 거라는 운영 방식이 사이트맵을 본문보다 훨씬 크게 만들고 있었습니다.
두 번째 페이지가 첫 페이지의 복제라고 신고되고 있었습니다
홈은 글을 10편씩 나눠 보여줍니다. 주소도 /blog?page=2, /blog?page=3처럼 페이지마다 따로 있습니다. 링크 구조만 보면 문제가 없어 보였습니다.
그런데 2페이지의 HTML을 확인하니 canonical이 1페이지를 가리키고 있었습니다.
<link rel="canonical" href="https://duolabs.co.kr/blog">2페이지에는 1페이지와 다른 글이 있습니다. 그런데 제가 검색엔진에 보내던 신호는 "이 페이지는 1페이지의 중복본입니다"에 가까웠습니다. 목록 아래에 이전·다음 링크를 넣어둔 것만 보고 페이지네이션 처리가 끝났다고 생각한 것이 잘못이었습니다.
Google의 페이지네이션 가이드도 페이지마다 고유 URL을 쓰고, 첫 페이지를 공통 canonical로 지정하지 말라고 설명합니다.
홈과 태그, 카테고리 목록이 현재 페이지 번호를 읽어 자기 주소를 canonical로 내보내도록 바꿨습니다. 배포 후 2페이지는 이렇게 응답합니다.
<title>전체 글 2페이지 · DUOLABS BLOG</title>
<link rel="canonical" href="https://duolabs.co.kr/blog?page=2">없는 페이지 번호는 404로 끝냅니다. 목록은 다르지만 canonical만 같은 어중간한 상태를 없앴습니다.
글은 186편인데 사이트맵 URL은 690개였습니다
더 큰 문제는 태그였습니다. 공개 데이터를 세어보니 결과가 이랬습니다.
| 항목 | 개수 |
|---|---|
| 발행 글 | 186 |
| 고유 태그 | 485 |
| 태그가 1개 글에만 쓰인 경우 | 372 |
| 태그가 1~2개 글에만 쓰인 경우 | 426 |
| 기존 사이트맵 전체 URL | 690 |
고유 태그의 76.7%가 글 하나에만 붙어 있었습니다. 글 한 편짜리 태그 페이지는 제목과 요약을 다시 보여주는 것 외에는 원문과 다른 가치가 거의 없습니다.
반대쪽도 문제였습니다. blog 태그에는 179편, technical-note에는 159편이 들어 있었습니다. 이 두 페이지는 블로그 홈과 거의 같은 목록입니다. model-* 태그는 어떤 모델을 사용했는지 남기는 작성 이력이지 독자가 찾는 주제도 아닙니다.
태그 자체를 지우지는 않았습니다. 글을 탐색하거나 작성 이력을 확인할 때는 여전히 쓸모가 있기 때문입니다. 대신 검색 색인과 탐색 UI의 역할을 나눴습니다.
- 글이 세 편 이상인 주제 태그만 사이트맵에 넣습니다.
- 글이 한두 편인 태그는
noindex, follow로 둡니다. blog,technical-note,model-*는 글 수와 관계없이 색인에서 제외합니다.- 제외된 태그 페이지의 글 링크는 그대로 따라갈 수 있게 합니다.
이 기준은 Google이 정한 숫자가 아니라 현재 콘텐츠 규모에 맞춰 제가 잡은 운영 기준입니다. Google의 사이트맵 가이드는 검색 결과에 보여주고 싶은 canonical URL을 사이트맵에 포함하라고 합니다. 그 원칙에 맞춰 얇은 분류 페이지를 덜어냈습니다.
새 사이트맵은 255개 URL입니다. 글 186편은 그대로 들어 있고, 태그 페이지는 485개에서 50개로 줄었습니다.
목록을 나누자 HTML이 1.37MB에서 175KB가 됐습니다
태그 색인만 줄인다고 페이지 자체가 가벼워지는 것은 아닙니다. blog 태그 페이지는 179편을 한 번에 렌더링하고 있었습니다. 각 카드에는 제목, 요약, 날짜, 카테고리와 모든 태그가 들어갑니다. Next.js의 서버 응답 데이터까지 합쳐지면서 비압축 HTML이 1,365,715바이트가 됐습니다.
태그와 카테고리 목록을 20편씩 나누고 각 페이지에 자기 canonical을 넣었습니다. 배포 후 같은 태그 첫 페이지는 175,219바이트였습니다. 약 87% 줄었습니다.
처음에는 태그 페이지를 색인에서 빼면 크롤 비용 문제도 끝난다고 생각할 수 있습니다. 하지만 noindex 페이지도 검색엔진은 지시를 확인하려고 방문하고, 사람도 내부 링크를 통해 들어옵니다. 색인 여부와 응답 크기는 별개로 다뤄야 했습니다.
피드는 보관함이 아니라 최근 글 목록으로 바꿨습니다
RSS, Atom, JSON Feed에는 186편의 전체 본문이 모두 들어 있었습니다. RSS 하나만 비압축 2,660,542바이트였습니다. 구독 클라이언트가 새 글을 확인할 때마다 과거 글 전체를 다시 받을 이유는 없습니다.
세 피드를 최근 50편으로 제한했습니다. RSS는 859,602바이트로 줄었습니다. 전체 글을 기계가 읽을 수 있는 출력이 필요한 경우를 위해 llms-full.txt는 그대로 유지했습니다. 목적이 다른 두 출력을 같은 정책으로 묶지 않았습니다.
저자 정보도 함께 맞췄습니다. 글 화면에는 사람 이름이 나오는데 Open Graph와 피드에는 조직 이름이 저자로 들어가 있었고, 본 사이트와 블로그가 서로 다른 조직 ID를 만들고 있었습니다. 이제 글의 저자는 한 사람으로, 발행자는 한 조직으로 같은 ID를 사용합니다. 구조화 데이터에서 정확하지 않던 wordCount와 본문 전체 복사도 제거했습니다.
코드를 고친 날이 SEO가 끝나는 날은 아닙니다
배포 뒤 공개 응답에서 다시 확인했습니다.
- 2페이지 canonical은 자기 주소를 가리킵니다.
- 얇은 태그에는
noindex, follow가 나갑니다. - 유효한 주제 태그는 계속 색인할 수 있습니다.
- 사이트맵에는 글 186편과 선별된 태그 50개가 들어 있습니다.
- RSS 항목은 50개입니다.
- 예전 블로그 주소의 홈은 새 주소로 한 번에 301 이동합니다.
테스트와 타입 검사, 프로덕션 빌드, 공개 크롤러 스모크 테스트도 통과했습니다. 다만 이것은 검색 순위가 올랐다는 결과가 아닙니다. 검색엔진이 잘못 해석할 여지를 줄였다는 배포 결과입니다.
이제 볼 것은 Search Console의 색인 보고서입니다. 얇은 태그가 색인 대상에서 빠지는지, 새 사이트맵에서 발견된 글이 실제로 색인되는지, 최근 주소 이전의 신호가 어느 속도로 합쳐지는지 확인해야 합니다. 이 수치는 시간이 지나야 나옵니다. 아직 모르는 부분을 성공으로 먼저 적지는 않으려고 합니다.
함께 읽기
- robots.txt는 무엇이고 왜 필요한가웹사이트를 운영하다 보면 루트 주소에서 robots.txt라는 작은 텍스트 파일을 만나게 됩니다. 내용은 몇 줄뿐인데 검색엔진 최적화 점검표에는 거의 빠지지 않고 등장합니다.
- 네이버 서치어드바이저 등록부터 사이트맵 제출까지웹사이트를 만들었다고 네이버 검색 결과에 바로 나타나는 것은 아닙니다. 검색로봇이 사이트를 발견하고, 문서를 수집하고, 색인할 수 있어야 합니다. 네이버 서치어드바이저는 이 과정을 직접 보장하는 등록 창구라기보다 사이트 소유권을 확인하고 수집·색인 상태를 점검하는 운영 도구에 가깝습니다.
- 블로그를 서브도메인에서 하위 경로로 옮긴 이유, 그리고 CSP가 애드센스를 막고 있었습니다회사 사이트와 기술 블로그를 따로 운영하는 구성은 흔합니다. 회사는 duolabs.co.kr, 블로그는 blog.duolabs.co.kr.
- 한국에서 접속하는데 Cloudflare가 로스앤젤레스에서 응답했습니다이미지 엣지 캐시를 손보고 나서 얼마나 빨라졌는지 재고 있었습니다. 캐시에서 나오면 156ms, 오리진까지 가면 622ms. 4배 차이니까 좋아하고 있었는데, 156ms라는 숫자가 자꾸 걸렸습니다.
- 로컬에서는 정상인데 운영에서만 깨진 AI 스트리밍문서 AI 기능을 로컬에서 완성하고 운영에 배포했을 때, 답변 자체는 정상인데 화면의 느낌이 완전히 달라졌습니다. 스트리밍 답변은 한꺼번에 나타났고, 전체 화면 패널은 작은 섹션 안에 갇혔으며, 모달이 열리자마자 배경 스크롤 잠금이 풀렸습니다.