3줄 요약 — 갓 만든 도메인에 서울 "카공 카페" 지도용 페이지 약 8,000개를 SSR로 띄웠다. 노출이 6일 만에 3 → 209로 올랐다가 ~0으로 붕괴했다. 버그가 아니었다. robots·canonical·fetch 전부 정상. 신규 도메인의 권위(authority) 벽이었고, 깨끗한 코드나
sitemap.xml수정으로는 못 뚫는다. 내가 점검한 전부, 바꾼 것, 그리고 불편한 교훈을 적는다.
배경
작은 한국 앱 커피콩 (CoffeeCong) 을 운영한다. 매일 마시는 커피 습관을 리워드로 바꿔주는 앱이다. 콘텐츠/SEO 전략으로 프로그래매틱 사이트를 하나 만들었다 — 서울의 공부하기 좋은 카페 지도. 자치구별·지하철역별·목적별(조용한 곳, 노트북 작업, 심야 등)로 탐색할 수 있다.
페이지 수는 조합에서 나온다:
자치구(25) × 목적(7) → 카테고리 허브 약 175개
+ 지하철역(약 300) → 역세권 허브
+ 개별 카페(약 6,000) → 카페당 1페이지
≈ 8,000 URL
아키텍처는 셸 주입(shell-injection) SSR 패턴이다 — 사용자에게는 인터랙티브 React 지도를 그대로 주고, 봇에게는 같은 셸에 완전히 렌더된 HTML 본문을 주입한다:
function renderSsr(data: PageData): string {
const template = readTemplate(); // SPA 셸 (<div id="root"></div> 보유)
const ssrBody = buildSsrBody(data); // h1 + 본문 + 디렉토리 링크
const headInject = `
<link rel="canonical" href="${canonical}" />
<meta name="robots" content="index, follow, max-image-preview:large" />
<script type="application/ld+json">${jsonLd}</script>`;
let html = template.replace('<div id="root"></div>', `<div id="root">${ssrBody}</div>`);
html = html.replace('</head>', `${headInject}</head>`);
return html;
}
그래서 모든 URL이 Googlebot에게 진짜 HTML이다: <h1>, 브레드크럼, JSON-LD, 내부 링크. 본문을 읽는 데 JS가 필요 없다. 사이트맵 제출 완료, robots.txt 깨끗함. 다들 "이건 꼭 제대로 해라"라고 말하는 그 부분 — 나는 제대로 했다.
상승, 그리고 절벽
다음은 Search Console의 실제 일별 노출 곡선이다:
| 날짜 | 노출 |
|---|---|
| 1일차 | 3 |
| 2일차 | 31 |
| 3일차 | 64 |
| 4일차 | 126 |
| 5일차 | 189 |
| 6일차 | 209 |
| 7일차 | 14 |
| 8일차~ | 2 → 0 → 0 |
"되고 있어!" 6일 — 그리고 절벽에서 떨어져 0에 수렴했다. 모든 사이트맵: 제출 N개, 색인 0개.
내 첫 직감은 당신과 같았다: 뭔가 망가졌다. 아니었다.
부검 (실제로 점검한 것)
루트·허브·카테고리 샘플에 URL Inspection API를 돌렸다. 모든 신호가 멀쩡했다:
verdict: NEUTRAL
coverageState: "Crawled - currently not indexed"
robotsTxtState: ALLOWED
indexingState: INDEXING_ALLOWED
pageFetchState: SUCCESSFUL
googleCanonical: (허브에서 userCanonical과 일치)
crawledAs: MOBILE
다시 읽어보자: SUCCESSFUL fetch, ALLOWED robots, INDEXING_ALLOWED — 그런데도 색인 안 됨. 구글이 페이지를 크롤했다. 단지 색인에 둘 가치가 없다고 판단했을 뿐이다.
이 "상승 후 붕괴" 모양은 알려진 패턴이다: 신규 도메인은 탐색 크롤 버스트(구글이 새 사이트맵을 일단 한번 훑음)를 받고, 도메인이 권위를 못 쌓았으면 구글이 색인 쿼터를 회수한다. 페이지들이 "Crawled - currently not indexed" 연옥으로 미끄러진다.
이게 "프로그래매틱 SEO로 페이지 1만 개 찍어라" 글에서 아무도 경고 안 해주는 부분이다: 크롤됨 ≠ 색인됨, 색인됨 ≠ 랭킹. 권위 있는 도메인에서는 대량 페이지가 후한 신뢰를 받는다. 생긴 지 4주 된 도메인에서는, 그냥 솎아내진다.
시도한 것 (그리고 그 값어치)
1. 사이트맵 가지치기 — 크롤 예산 집중.
사이트맵 인덱스에 URL이 약 8,000개였다. 대부분은 노출 0인 얇은 개별 카페 페이지였고, 카테고리/역세권 허브가 사실상 모든 노출을 가져갔다. 그래서 사이트맵을 허브 위주(약 560개, 90%+ 감축)로 줄였다. 카페 라우트는 여전히 존재하고 내부 링크로 크롤 가능하다 — 이건 de-submit이지 de-index가 아니다. 목표: 쥐꼬리만 한 크롤 예산을 8천 개 얇은 페이지에 흩뿌리지 말고, 노출을 버는 소수에 집중시키기.
값어치? 있다. 단 이건 초점 맞추기지 해결책은 아니다.
2. 내부 링크 — 내가 놓친 갭.
가장 권위 높은 페이지(홈/랜딩)가 모든 콘텐츠 기둥에 링크하고 있었다... 그 8,000페이지 지도만 빼고. 전형적인 실수. 정작 핵심 섹션은 사이트 상단에서 들어오는 내부 링크가 0개였다. 고쳤고, 허브 간 크로스링크도 추가했다.
값어치? 진짜로 필요하다(고립된 섹션은 크롤 우선순위를 못 받는다) — 하지만 내부 링크는 외부 권위를 만들지 못한다.
3. canonical 정리.
한 stale 스냅샷에서 루트의 userCanonical(/landing/)과 googleCanonical(/)이 어긋나 있었다. 구글이 추측하지 않게 해소할 가치는 있지만, 본 게임이 아니라 곁다리였다.
4. 내가 계속 바랐던 것: "이거 색인해줘" API.
일반 페이지용은 없다. 구글의 Indexing API는 JobPosting과 BroadcastEvent 구조화 데이터에만 허용된다. 카페 디렉토리에 쓰는 건 정책 위반이고 동작도 안 한다. 진짜 버튼은 GSC UI의 수동 "색인 요청", 하루 약 10건뿐이고, 그것도 크롤을 살짝 유도할 뿐 색인을 보장하지 않는다.
불편한 교훈
기술적 정리를 다 하고 나면, 진단은 지루하고 겸허해진다:
갓 만든 도메인은 물량으로 색인을 뚫을 수 없다. 권위 + 콘텐츠 고유성 + 시간 — 대략 이 순서로 들어간다. 그리고 프로그래매틱 페이지는 고유성 허들을 더 높인다, 낮추는 게 아니라 — 템플릿 콘텐츠는 도메인이 신뢰를 얻기 전까지는 저가치로 읽히기 때문이다.
구체적으로, 실제로 바늘을 움직이는 레버:
-
외부 editorial 백링크. 이게 진짜 병목이다. 스토어 리스팅 링크 아님(그건
nofollow라 거의 안 넘어감), 같은-회사 크로스 프로모션 아님(구글은 같은 주체가 소유한 사이트 간 링크를 강하게 할인한다). 진짜 사이트에서 오는, 주제 관련성 있는 독립 링크. - 템플릿 모양이 아닌 콘텐츠. 고유 데이터, 실제 사진, N번째 페이지를 N+1번째와 구별되게 만드는 무엇이든.
- 시간. 신규 도메인 "샌드박스"는 실재한다. 다 잘해도 2~6개월 잡아라.
- 우선순위 페이지 몇 개에 대한 수동 색인 요청 — 치료가 아니라 넛지.
생산적으로 느껴지지만 색인엔 거의 영향 없는 것들: 가짜 lastmod 신선도로 사이트맵 재생성, 핑, 같은 사이트맵 재제출, JSON-LD 더 추가하기. 한 번 제대로 해두고, 그다음엔 그만 만져라.
그래서, 성공했나?
솔직히 — 이 글을 쓰는 지금도 연옥에 있다. 기술 토대는 탄탄하고(그 부분은 내 통제 안에 있다), 권위 작업은 결과를 가르는 느린 노가다다. 위 아키텍처의 라이브 결과가 궁금하면 허브는 여기다: 서울 카공 카페 지도. 몇 달 뒤 다시 와서, 이 부검이 회복으로 바뀌었는지 보자.
프로그래매틱 사이트를 신규 도메인의 벽 너머로 밀어 올려본 적 있다면, 무엇이 결정적이었는지 댓글로 정말 듣고 싶다 — 백링크였나, 콘텐츠 깊이였나, 아니면 그냥 인내였나?
Top comments (0)