368장 중 306장은 색인을 거절한다 — 리포트 robots × sitemap 교차 전수
웹 지면이 검색엔진에 색인 의사를 전하는 길은 서로 독립된 셋이다. 지면 자신의 <meta name="robots">, 사이트가 제출하는 sitemap.xml, 그리고 robots.txt의 크롤 차단. 셋은 각자 다른 층에서 작동하므로 얼마든지 엇나갈 수 있다 — sitemap에 올려 놓고 지면은 noindex라고 적는 조합, 색인해 달라고 적어 놓고 sitemap에서는 빼놓는 조합이 그렇다. 둘 다 흔한 실수이고, 전자는 신호가 서로를 지우며 후자는 발견 자체를 늦춘다.
그래서 질문은 교차표로 세웠다. 칼라톤 시즌 리포트의 지면 하나하나는 네 조합 가운데 어디에 있는가. 바로 전 회차에서 리포트 노출 구조를 셀 때 이 교차는 일부러 미뤄 두었다 — 그 글은 인물 아카이브 두 장이 noindex, follow이고 sitemap의 리포트 항목이 62개라는 두 수까지만 확인하고, 리포트 한 장 한 장의 robots 메타와 등재를 맞춰 보지는 않았다. 이번에는 368장 전부를 양쪽에서 읽어 칸에 넣었다.
결과는 표를 만들 필요가 거의 없는 모양이었다. 네 칸 가운데 두 칸이 비어 있다.
교차표 — 네 조합 중 둘은 0장
Frontend/public/reports/ 아래의 HTML은 368장이다. 시즌 리포트 364개(국문 182편 + 영문 182편)와 인덱스 4장(최상위 국·영문 2장, 인물 모듈 전용 아카이브 국·영문 2장). 368장 전부가 robots 메타를 명시하고 있었고 — 메타가 없는 지면은 0장 — 값은 두 가지뿐이었다.
| robots 메타 | sitemap 등재 | sitemap 미등재 | 합 |
|---|---|---|---|
index, follow | 62 | 0 | 62 |
noindex, follow | 0 | 306 | 306 |
| 합 | 62 | 306 | 368 |
개수만 맞은 것이 아니다. 두 집합을 주소 단위로 양방향 차집합까지 냈더니 index, follow인데 sitemap에 없는 지면 0장, sitemap에 있는데 noindex인 지면 0장, sitemap에 올라 있는데 디스크에 파일이 없는 주소 0건이었다. 62장과 62개 주소는 그냥 같은 집합이다.
세 번째 층인 robots.txt는 아예 이 이야기에 끼지 않았다. 파일에는 Disallow가 20줄 있는데 리포트 경로를 가리키는 줄은 0줄이고, 20줄은 전부 관리자 전용 화면 10종(제작 스튜디오·숏폼·성경·법의 바다·롱폼 등, 확장자 있는 주소와 없는 주소를 각각 한 줄씩)이다. 리포트 306장은 크롤은 허용되고 색인만 거절되는 상태다. 이건 사고가 아니라 선택이다 — 차단했다면 뒤에 이어지는 follow가 아무 뜻이 없어진다.
그 선택을 하는 코드는 한 줄이다
칸 두 개가 빈 이유는 리포트 생성기를 보면 바로 나온다. 지면의 틀을 찍는 함수 pageShell은 noindex라는 불리언 하나를 받아 메타 태그를 noindex, follow와 index, follow로 가른다. sitemap을 쓰는 쪽은 별개 함수인데, 거기서도 조건이 한 줄이다 — 모듈 슬러그가 character면 건너뛴다. 소스에 달린 주석은 이유를 이렇게 적어 두었다.
character 리포트는 시즌 간 근중복 템플릿 페이지가 매일 증식하는 구조 → 페이지 자체도 noindex 이며 sitemap 에서도 제외한다(AdSense 저가치/스케일드 콘텐츠 판정 방지). 리포트 인덱스에서는 계속 링크됨.
두 산출물이 같은 판단에서 나오니 어긋날 자리가 없다. 교차표의 0 두 개는 '우리가 정책을 잘 지켰다'는 증거가 아니라 '두 신호가 애초에 한 플래그의 그림자다'라는 사실의 다른 표현이다.
같은 플래그가 광고까지 끈다
예상하지 못한 쪽은 세 번째 산출물이었다. 같은 noindex 불리언이 AdSense 스니펫의 삽입 여부도 가른다. 그래서 368장을 광고 기준으로 다시 세면 교차표가 한 번 더 겹친다.
| robots 메타 | 지면 | AdSense 스니펫 | 미로드 주석 | JSON-LD |
|---|---|---|---|---|
index, follow | 62 | 62 | 0 | 62 |
noindex, follow | 306 | 0 | 306 | 304 |
광고가 실린 지면과 색인을 요청한 지면은 글자 하나 차이 없이 같은 62장이다. 나머지 306장에는 스니펫 대신 <!-- Google AdSense: noindex 템플릿 페이지 — 광고 미로드 --> 주석이 들어 있다 — 306장 전부, 예외 0장. 광고 수익을 포기한 지면과 색인을 포기한 지면이 완전히 같은 집합이라는 뜻이고, 이건 저가치 콘텐츠 판정을 피하려는 쪽에서 보면 일관된 선택이다.
마지막 열에서만 1:1이 깨진다. JSON-LD 구조화 데이터는 366장에 있고 없는 두 장은 인물 아카이브 국·영문이다(생성기가 그 둘에만 jsonLd: null을 넘긴다). 그래서 이 두 장은 색인도 요청하지 않고 구조화 데이터도 없는, 368장 가운데 유일한 조합이다 — 그러면서 인물 리포트 304장으로 가는 유일한 목록이기도 하다.
어느 쪽이 306장인가 — 모듈 하나다
306 대 62를 가르는 축이 무엇인지가 다음 질문이다. 언어도 아니고 날짜도 아니고 시즌 수도 아니었다. 모듈 하나다.
| 지면 묶음 | 장수(국문+영문) | robots | sitemap | AdSense |
|---|---|---|---|---|
| 인물 생성 시즌 리포트 | 304 | noindex, follow | 0 | 미로드 |
| 인물 생성 아카이브 인덱스 | 2 | noindex, follow | 0 | 미로드 |
| AI 유머 | 42 | index, follow | 42 | 로드 |
| AI 커플매칭 | 12 | index, follow | 12 | 로드 |
| 가상 트레이딩 | 4 | index, follow | 4 | 로드 |
| 마인드게임(홀짝) | 2 | index, follow | 2 | 로드 |
| 리포트 최상위 인덱스 | 2 | index, follow | 2 | 로드 |
| 합 | 368 | — | 62 | — |
인물 생성 모듈이 리포트 지면의 83.2%(306/368)를 차지하고, 그 전부가 색인 대상에서 빠져 있다. 다섯 모듈 가운데 넷은 100% 등재이고 하나는 0% 등재다 — 중간값이 없다. 이 숫자가 커지는 방향도 한쪽이다. 늘어나는 리포트가 전부 인물 모듈이므로, 아무것도 바꾸지 않으면 83.2%는 내일 더 올라간다.
follow는 무엇을 열어 두었나 — 2홉, 그리고 측면 이동 0
306장이 noindex이면서 nofollow가 아닌 것은 의미가 있다. 크롤러가 그 지면을 색인하지 않고도 링크는 따라가 달라는 요청이다. 그래서 실제 경로가 얼마나 열려 있는지를 국문 182편으로 세었다.
| 국문 리포트가 놓인 자리 | 편수 | 비율 | 지나는 지면의 색인 신호 |
|---|---|---|---|
| 최상위 인덱스에서 1홉 — 네 모듈 전량 | 30 | 16.5% | index → index |
| 최상위 인덱스에서 1홉 — 인물 최신 8편 | 8 | 4.4% | index → noindex |
| 인물 아카이브를 거쳐 2홉 | 144 | 79.1% | index → noindex → noindex |
인물 리포트 152편 중 색인 대상 지면에서 한 번에 닿는 것은 8편(5.3%)뿐이고, 144편(94.7%)은 자신도 색인 대상이 아닌 아카이브를 한 번 지나야 한다. 상한 8은 생성기 상수로 박혀 있고, 그 값을 정한 이유도 같은 '근중복 템플릿' 판단이다.
그리고 이 구조에는 측면 이동이 없다. 인물 시즌 리포트 304장에서 다른 시즌 리포트를 가리키는 링크는 0건이었다(자기 언어쌍 제외). 리포트 한 장의 바깥 행선지는 최상위 인덱스와 자기 언어 짝뿐이다. 368장 전부가 rel="alternate"를 세 개씩 달고 있으니 국문↔영문 이동은 보장되지만, 그 이동은 같은 내용의 다른 언어판으로 가는 것이라 새 지면을 열어 주지 않는다. 결국 follow가 실제로 여는 길은 아카이브 한 장을 분기점으로 하는 깊이 2의 부채꼴이고, 그 분기점 자체가 noindex다.
곁가지 — sitemap은 한 파일 안에서 두 가지 규약을 쓴다
등재 여부가 깔끔했으니 등재된 주소의 모양도 확인했다. 리포트 62개는 자기 지면의 canonical과 62/62 글자까지 일치했다 — sitemap과 canonical이 어긋나는 흔한 결함은 없다. 다만 그 일치 안에 비대칭이 하나 들어 있다. 국문 인덱스는 디렉터리형 /reports/, 영문 인덱스는 파일형 /reports/index-en.html이고, 인물 아카이브도 같은 쌍(/reports/character/ 대 /reports/character/index-en.html)이다. 영문에는 디렉터리 주소가 없으니 구조적인 비대칭이지, 불일치는 아니다.
범위를 sitemap 전체로 넓히면 규약이 둘로 갈린다. 153개 주소 가운데 .html로 끝나는 것이 136개, 그렇지 않은 것이 17개다. 확장자를 떼어 쓰는 17개는 /·/articles/·/reports/와 최상위 페이지 14장(/about·/humor·/lotto 등)이고, 그 14장은 전부 디스크에 .html 파일로 있으면서 자신의 canonical도 확장자 없는 쪽으로 적는다. 즉 리포트·아티클 블록은 .html을 붙이고 최상위 페이지 블록은 떼는, 두 규약이 한 파일에 공존한다. 둘 다 각자의 canonical과는 맞으므로 모순은 아니지만, 같은 사이트맵을 읽는 사람이 규칙을 하나로 외울 수는 없다.
마지막으로 한 가지가 눈에 걸렸다. '색인 의사를 명시하는가'를 sitemap 152개 전부에 물으면 묶음별로 갈린다.
| 묶음 | sitemap 등재 | robots 메타 명시 | 명시율 |
|---|---|---|---|
| 시즌 리포트 | 62 | 62 | 100% |
| 아티클(목록 포함) | 76 | 76 | 100% |
| 최상위 페이지 | 15 | 1 | 6.7% |
생성기가 찍는 지면(리포트·아티클)은 138장 전부가 robots를 적는다. 사람이 손으로 쓴 최상위 15장 가운데 적는 것은 /about 한 장뿐이고, 나머지 14장 — 홈과 서비스 페이지 7장, 이용약관·개인정보처리방침 국·영문 4장, 이용 가이드 국·영문 2장 — 에는 메타가 없다. 메타가 없으면 기본값이 색인 허용이므로 지금 동작에는 문제가 없다. 다만 그 4장(약관·방침 국·영문)은 canonical도 없다. 자동으로 찍히는 지면의 엄격함이 손으로 쓴 지면에는 적용되지 않았다는 쪽이 정확한 서술이다.
한계 — 이 분석이 말할 수 없는 것
첫째, 교차표의 0 두 칸은 독립 검증이 아니다. robots 메타와 sitemap 등재가 생성기의 같은 판단에서 나오므로 어긋날 자리가 구조적으로 없다. 이 통과를 '정책이 정확히 집행됐다'로 읽는 것은 맞지만 '두 신호가 서로를 검증했다'로 읽으면 틀린다. 둘 다 틀리는 경우에는 교차표도 똑같이 깨끗해 보인다.
둘째, 검색엔진이 실제로 무엇을 색인했는지는 모른다. noindex와 sitemap 등재는 우리가 보내는 요청이고, 결과는 Search Console에서만 확인된다. 306장이 색인되지 않았다는 보장도, 62장이 색인됐다는 보장도 이 데이터에는 없다.
셋째, follow가 실제로 어떤 효과를 내는지 측정하지 않았다. 홉 수는 링크 구조의 수치이지 크롤 빈도나 링크 가치 전달의 수치가 아니다. '2홉'이 '늦게 발견된다'는 뜻인지, 얼마나 늦는지는 이 집계로 계산할 수 없다.
넷째, AdSense 쪽도 스니펫의 유무까지만 셌다. 306장에서 광고를 빼는 선택이 저가치 판정을 실제로 막아 주고 있는지, 62장에 실린 광고가 얼마를 벌어 주는지는 전혀 다른 데이터다. 코드 주석이 말하는 의도를 성과로 옮겨 읽지 말아야 한다.
다섯째, 메타가 없는 최상위 14장을 '빠뜨림'이라고 단정할 수 없다. 기본값이 색인 허용이므로 의도적 생략과 누락이 파일에서는 똑같이 보인다. 이 글이 말할 수 있는 것은 '명시율이 100%와 6.7%로 갈린다'까지다.
여섯째, 비율의 분모가 매시 커진다. 83.2%·16.8%는 2026년 10월 29일 기준이고, 늘어나는 리포트가 전부 인물 모듈이므로 색인 대상 비율은 계속 내려간다. 이 숫자를 인용할 때는 집계 날짜를 함께 적어야 한다.
일곱째, 성인 시즌 리포트는 공개 대상이 아니므로 집계에 들어 있지 않다. 368장은 공개 지면의 전량이지 제작된 시즌의 전량이 아니다.
그래서 무엇을 할 것인가
점검 자체는 통과다. 신호가 서로를 지우는 조합(sitemap에 올려 두고 noindex)도, 발견을 늦추는 조합(index인데 미등재)도 0장이었고, 크롤 차단과 색인 거절을 혼동한 자리도 없었다. 네 조합을 기대하고 만든 표가 두 칸으로 끝난 것은 좋은 결과다 — 색인 정책은 운영자가 지면마다 기억해야 하는 표가 아니라 모듈 하나에 붙은 플래그 하나이고, 그래서 틀릴 여지가 거의 없다.
남는 것은 그 플래그가 묶어 놓은 세 가지가 계속 한 묶음이어야 하느냐는 질문이다. 지금은 '색인하지 않는다'와 '광고를 싣지 않는다'와 'sitemap에 올리지 않는다'가 한 번의 판단으로 결정된다. 근중복 템플릿 지면이라는 판단이 유지되는 동안에는 셋이 같이 가는 쪽이 맞다. 다만 인물 리포트가 템플릿을 벗고 편마다 다른 내용을 담게 되는 날이 오면, 세 스위치를 동시에 뒤집는 것이 아니라 하나씩 떼어 볼 수 있어야 한다 — 그러려면 플래그가 하나라는 지금의 단순함이 그때는 비용이 된다.
그 전에 손이 덜 가는 쪽이 하나 있다. 아카이브 두 장이다. 인물 리포트 304장으로 가는 유일한 분기점이면서 자신도 noindex이고 JSON-LD도 없는 유일한 지면이 그 둘이다. 상한 8을 올리지 않고도 이 분기점의 신호만 다시 볼 수 있고, 거기서 바뀌는 것이 144편의 깊이다.
그리고 손으로 쓴 최상위 14장에는 robots 한 줄을 적어 둘 때가 됐다. 지금 동작이 맞는 것과, 의도가 지면에 적혀 있는 것은 다른 일이다.
집계 기준. 2026년 10월 29일(KST) 기준 저장소 Frontend/public/reports/의 HTML 368장 — 공개 시즌 리포트 364개(국문 182 + 영문 182: 인물 생성 152 · 유머 21 · 커플매칭 6 · 트레이딩 2 · 마인드게임 1과 그 영문판)와 인덱스 4장(/reports/, /reports/index-en.html, /reports/character/, /reports/character/index-en.html) — 을 전수 파싱했다. robots 값은 각 지면 첫 <meta name="robots">의 content를 그대로 읽었다. sitemap 등재는 Frontend/public/sitemap.xml의 <loc> 전수(이 글의 URL을 등록한 뒤 153개, 그중 리포트 62개 · 아티클 76개 · 최상위 15개)에서 셌고, 주소를 디스크 파일로 되돌릴 때 디렉터리형은 index.html로, 확장자 없는 주소는 같은 이름의 .html로 해석했다. 양방향 차집합은 이 해석 후의 주소 문자열 기준이다. AdSense는 ca-pub-5753353429019665 문자열의 유무로, JSON-LD는 application/ld+json 블록의 유무로 판정했다. 홉 수는 각 인덱스 본문의 href가 /reports/{모듈}/season-{번호}.html 형태로 그 리포트를 가리키는지로 셌고 내비게이션·푸터 링크는 제외했다. 교차 시즌 링크 0건은 리포트 304장에서 자기 파일명(언어쌍 포함)을 제외한 season-*.html 링크를 센 결과다. robots.txt의 Disallow 20줄과 생성기의 noindex 분기·sitemap 제외 조건·목록 상한 상수는 저장소 소스에서 확인했다. 비율은 소수점 아래 첫째 자리에서 반올림했다. 성인 시즌 리포트는 공개 대상이 아니므로 집계에 포함되지 않는다. 아티클 76개는 이 글이 추가된 뒤의 수이며, 추가 전에는 75개(발행 74편 + 목록 1장)였다.