7,274페이지 프로그래매틱 SEO 사이트를 혼자 운영하며 겪은 일들

직장을 다니면서 사이드 프로젝트로 hazelfy라는 사이트를 운영하고 있습니다. 공공데이터를 기반으로 전국 주차장 정보를 정리해서 보여주는 프로그래매틱 SEO 사이트인데, 현재 서울·경기 지역 기준으로 약 7,274개의 페이지가 배포되어 있습니다.

페이지를 코드로 찍어내는 것 자체는 어렵지 않습니다. 진짜 이야기는 그다음부터 시작됩니다. 이 글은 지난 몇 달간 이 사이트를 운영하면서 겪은 사건들 — 스팸 사이트에게 캐노니컬을 하이재킹당한 일, 구글과 네이버의 인덱싱 속도가 극단적으로 갈린 일, 검색결과에 내 사이트만 지도 썸네일이 뜨게 된 이유, 그리고 위치기반서비스 신고까지 — 를 한 번에 정리한 운영기입니다.


1. 인프라: Cloudflare Pages에서 Worker + R2로 갈아탄 이유

처음에는 Next.js SSG로 빌드한 결과물을 Cloudflare Pages에 올렸습니다. 무료고, 배포도 간단하니까요. 문제는 페이지 수가 수천 단위로 늘어나면서 나타났습니다. 매번 전체 빌드 아티팩트를 통째로 업로드하는 구조는 페이지가 많아질수록 배포 시간이 감당이 안 됩니다.

그래서 구조를 바꿨습니다.

  • 정적 파일은 R2 버킷에 저장
  • 요청을 받아 R2에서 파일을 서빙하는 얇은 Worker 하나
  • 배포는 GitHub Actions에서 rclone 델타 싱크로 변경된 파일만 업로드

이 구조의 장점은 배포가 “전체 업로드”에서 “변경분 동기화”로 바뀐다는 점입니다. 7천 페이지 중 오늘 바뀐 게 50페이지라면 50개만 올라갑니다. 데이터가 주기적으로 갱신되는 프로그래매틱 사이트에서는 이 차이가 큽니다. 그리고 서빙 로직이 Worker 코드로 내 손에 들어오기 때문에, 뒤에 나올 하이재킹 방어 같은 대응도 유연해집니다.

비용은 R2 스토리지 + Worker 요청 과금인데, 이 규모에서는 사실상 무료 티어 안에서 다 해결됩니다.

2. 어느 날 도박 사이트가 내 페이지를 통째로 미러링하기 시작했다

어느 날 서치콘솔을 보다가 이상한 걸 발견했습니다. 처음 보는 도메인(747live.bet — 그렇습니다, 도박 사이트입니다)이 hazelfy의 주차장 페이지들을 URL 구조까지 그대로 미러링하고 있었습니다.

이게 왜 위험하냐면, 단순 카피가 아니라 캐노니컬 하이재킹으로 이어질 수 있기 때문입니다. 스팸 도메인이 내 콘텐츠를 실시간으로 프록시해서 보여주면, 검색엔진이 원본 판별에 혼란을 일으켜 최악의 경우 스팸 쪽을 원본으로 인식하고 내 페이지를 중복 문서로 처리해버립니다. 도메인 권위가 낮은 신생 사이트일수록 당하기 쉽습니다.

대응은 Cloudflare WAF에서 했습니다. 미러링 사이트는 결국 자기 서버에서 내 오리진으로 요청을 보내 콘텐츠를 가져가는 구조이기 때문에, 요청 패턴을 잡아서 차단하면 됩니다.

  • 정상적인 브라우저/검색엔진 봇이 아닌 요청 패턴 필터링
  • 의심 트래픽의 Referer / ASN 기반 차단 룰
  • 검증된 검색엔진 크롤러(Googlebot, Yeti 등)는 화이트리스트로 보호

차단 이후 미러 사이트는 내 콘텐츠를 더 이상 가져가지 못하게 됐고, 미러링된 페이지들은 갱신이 끊긴 채 방치되다 검색에서 밀려났습니다. 이런 일을 겪고 나니, 서빙 레이어를 Worker로 직접 쥐고 있었던 게 다행이었습니다. 정적 호스팅만 썼다면 대응 수단이 훨씬 제한적이었을 겁니다.

혹시 프로그래매틱 사이트를 운영 중이라면, 가끔 자기 사이트의 고유 문장을 따옴표로 묶어 검색해보세요. 하이재킹은 조기에 발견할수록 피해가 적습니다.

3. 검색결과에 왜 내 사이트만 지도 썸네일이 뜰까

최근에 재미있는 걸 발견했습니다. 같은 키워드의 검색결과에서 경쟁 사이트들은 텍스트만 나오는데, hazelfy의 주차장 상세 페이지만 지도 썸네일이 함께 노출되고 있었습니다.

이유는 단순합니다. 지도를 정적 이미지로 서빙하느냐, JS로 런타임에 그리느냐의 차이입니다.

대부분의 사이트는 카카오맵이나 네이버맵 JS SDK로 지도를 렌더링합니다. 사용자 눈에는 지도가 보이지만, 크롤러 입장에서는 빈 div와 canvas만 있을 뿐 “가져갈 이미지”가 없습니다. 반면 정적 지도 이미지를 img 태그로 넣고 og:image까지 페이지별로 지정해두면, 검색엔진이 그 이미지를 대표 썸네일로 채택할 수 있습니다.

html

<!-- 크롤러가 썸네일로 가져갈 수 있는 형태 -->
<meta property="og:image" content="https://.../maps/sewoon-parking.png" />
<img src="/maps/sewoon-parking.png" alt="세운상가밀 공영주차장 위치 지도" />

주차장처럼 “위치”가 곧 답인 검색에서 지도 썸네일은 클릭 전에 이미 정보의 일부를 보여줍니다. 썸네일이 있는 결과와 없는 결과의 CTR 차이는 결코 작지 않습니다. 프로그래매틱 SEO를 한다면, 페이지마다 고유한 대표 이미지를 크롤링 가능한 형태로 제공하는 것만으로 검색결과에서 시각적 우위를 가져갈 수 있습니다.

4. 구글 인덱싱 6% vs 네이버: 같은 사이트, 다른 대접

같은 7,274페이지를 두고 두 검색엔진의 반응이 극단적으로 갈렸습니다.

구글: 인덱싱된 페이지 약 450개, 비율로는 6% 수준. 나머지 6,795페이지는 “발견됨 – 현재 색인이 생성되지 않음(Discovered – currently not indexed)” 상태에 묶여 있습니다. 콘텐츠 품질 문제가 아니라 — 색인된 페이지들은 정상적으로 검색 노출되고 있습니다 — 신생 도메인의 크롤 버짓과 도메인 권위 문제입니다. 구글은 권위가 낮은 도메인에 크롤 자원을 아끼기 때문에, 페이지를 아무리 많이 만들어도 “일단 대기열에 넣어두는” 상태가 지속됩니다.

네이버: 훨씬 적극적으로 색인해주고 있고, 현재 일 노출 9,000회 이상으로 사실상 주력 트래픽 엔진 역할을 하고 있습니다.

여기서 얻은 교훈은 두 가지입니다.

첫째, 한국어 프로그래매틱 사이트라면 초기 트래픽 전략에서 네이버를 과소평가하면 안 됩니다. 구글 인덱싱이 풀리기를 기다리는 동안 네이버가 사이트를 먹여 살릴 수 있습니다.

둘째, 구글의 “Discovered – not indexed”는 기술적 수단(사이트맵 재제출, 색인 요청 API 등)으로 뚫리는 문제가 아닙니다. 결국 백링크와 콘텐츠로 도메인 권위를 쌓아 크롤 버짓 자체를 키우는 장기전입니다. 그래서 이런 글을 쓰고 있기도 하고요.

이 부분은 수치가 유의미하게 개선되면 별도의 해결기로 다시 정리할 예정입니다.

5. 보너스: 개발자가 직접 해본 위치기반서비스 간이신고

주차장 정보를 넘어서 실시간 위치 공유 기능(“지금어디야”)을 붙이면서 예상 못 한 벽을 만났습니다. 코드가 아니라 규제였습니다.

한국에서 위치정보를 수집·이용하는 서비스는 위치정보법에 따라 방송통신위원회에 신고 의무가 있습니다. 개인 위치정보를 다루는 소규모 서비스는 위치기반서비스사업 간이신고 대상이 될 수 있는데, 사업자 정보와 서비스 내용, 위치정보 처리 방식 등을 정리해 제출하는 절차입니다. 여기에 더해 서비스 내에는 위치정보 수집·이용 동의 UI와 관련 약관을 갖춰야 합니다.

개발자 입장에서 정리하면: 위치 기능은 “GPS 좌표 받아오기”가 아니라 “신고 + 약관 + 동의 플로우 + 기능 구현”의 패키지입니다. 기능 개발 일정에 규제 대응 기간을 처음부터 포함해두는 것을 권합니다. 이 절차의 상세한 후기는 분량이 있어서 다음 글에서 따로 다루겠습니다.


마치며

프로그래매틱 SEO는 “페이지를 대량 생성하면 트래픽이 온다”는 단순한 그림으로 시작하지만, 실제 운영은 인프라 설계, 스팸 방어, 검색엔진별 전략, 심지어 규제 대응까지 걸쳐 있는 종합 격투기에 가깝습니다.

그래도 하나씩 문제를 풀어가는 재미가 있습니다. hazelfy는 지금도 서울·경기의 주차장 데이터를 매일 갱신하며 돌아가고 있고, 다음 버티컬(약국, 병원)로의 확장도 준비 중입니다. 각 주제의 자세한 후속편은 이어지는 글에서 다루겠습니다.

궁금한 점이나 비슷한 경험이 있다면 댓글로 나눠주세요.