개발일지WhoAmI — 공부 기록을 발행 가능한 형태로 좁힌 파이프라인 →
「비공개 모드」라고 이름 붙였는데 비공개가 아니었다
도메인을 붙이고 나서 사이트를 검색에서 빼는 스위치를 넣고 「비공개 모드」라고 불렀다. 이름을 「색인 차단 모드」로 고친 뒤, Google 공식 문서와 현재 Worker 구성을 다시 대조해 이 설정이 보장하지 않는 범위까지 정리했다.
목차 3
2026-08-09 업데이트 —
robots.txt와noindex의 상호작용을 바로잡고, 정적 호스팅에서도 별도 인증 계층을 둘 수 있음을 반영했다. 현재 색인 점검과 Worker 적용 범위의 한계도 명확히 했다.
이 사이트는 옵시디언에 쌓인 노트를 정리해 두려고 만들었다. 배포는 목표가 아니라 확인해 보고 싶은 대상 쪽에 가까웠고, 로컬에만 두면 글이 안 쌓인다는 이유도 있었다.
7월 30일에 도메인을 연결했다. 그때부터는 「무엇을 만들까」보다 「무엇이 나가 있나」를 먼저 봐야 하는 물건이 된다. 주소를 아는 사람이 열 수 있고, 크롤러도 주소를 찾을 수 있다. 그래서 배포된 응답을 하나씩 열어 봤는데, 배포 계획 문서에는 「아직 공개하지 않았다」고 적혀 있던 반면 실제 robots.txt 는 Allow: / 였다.
문서의 그 문장이 가리킨 사실은 「검색엔진에 제출하지 않았다」였다. 제출과 차단은 다르다. 크롤러는 제출을 기다리지 않고, 인증서 투명성 로그나 DNS 기록으로도 새 도메인을 찾는다. 「아직 안 올렸다」를 「아직 안 열렸다」로 적고 있었던 셈이다.
세 장치를 함께 내보내도 「3중 보장」은 아니다
현재 색인 차단 빌드는 세 종류의 지시를 함께 내보낸다. 다만 역할이 다르고, Google에서는 서로 충돌하는 지점도 있다.
| 어디 | 무엇 | 확인해야 할 한계 |
|---|---|---|
BaseLayout.astro |
전 페이지 <meta name="robots" content="noindex, nofollow"> |
HTML 응답을 크롤러가 가져가야 읽을 수 있다 |
robots.txt.ts |
Disallow: /, Sitemap 줄 제거 |
크롤을 막는 지시다. 외부 링크로 알려진 URL 자체는 검색 결과에 남을 수 있다 |
astro.config.mjs → dist/_headers |
전 경로 X-Robots-Tag: noindex, nofollow, noarchive |
비HTML 응답에도 쓸 수 있지만, 이 헤더도 크롤러가 URL을 요청해야 읽힌다 |
Google의 robots 메타·X-Robots-Tag 문서는 크롤이 허용된 URL에서만 이 지시를 읽을 수 있다고 명시한다. robots.txt로 URL을 막으면 Googlebot은 페이지를 요청하지 않으므로 본문의 메타와 응답 헤더에 있는 noindex를 확인하지 못한다. 따라서 Disallow: /와 noindex를 단순히 겹쳤다고 색인 제외가 세 겹으로 보장되는 것은 아니다. Google의 robots.txt 안내대로, 차단된 URL이 외부 링크에서 발견되면 내용 없이 URL만 색인될 수도 있다.
세 출력을 src/data/site.ts의 값 하나에 묶은 것은 설정 누락을 줄이는 데에는 유효하다.
export const SEARCH_BLOCKED = process.env.SITE_PUBLIC !== '1';환경변수를 빠뜨려도 색인 차단 지시가 포함된 산출물이 만들어지도록 기본값을 정했다.
값은 손으로 넣지 않고 npm run deploy / npm run deploy:public 처럼 명령 이름으로 고른다. PowerShell 에서 $env:SITE_PUBLIC 은 창이 살아 있는 동안 남기 때문에, 같은 창에서 다시 배포하면 의도치 않게 색인이 켜지고 새 창에서 배포하면 켜 둔 사이트가 noindex 로 덮인다. 어느 쪽이든 「내가 마지막에 무엇을 세팅했더라」를 기억해야 하는 구조였다.
scripts/check-exposure.mjs가 검사하는 것도 이 범위다. 로컬 dist에 Disallow: /, _headers의 X-Robots-Tag, 각 HTML의 noindex가 있는지와 숨긴 경로가 사이트맵·링크에 섞이지 않았는지를 본다. 배포된 응답을 Googlebot이 가져갔는지, 지시를 처리했는지, 실제 색인에서 빠졌는지는 확인하지 않는다. 이 검사의 통과는 산출물에 지시가 존재한다는 빌드 게이트이지 색인 제외 결과의 보장은 아니다.
비공개라는 이름을 색인 차단으로 바꿨다
8월 3일에 이 스위치를 넣고 배포하면서 「비공개 모드」라고 불렀다. 그러고 나서 이 모드가 실제로 무엇을 보장하는지 따져 보니 이름이 동작보다 많은 것을 약속하고 있었다.
현재 구현이 시도하는 것은 검색 노출 억제까지다. 주소를 아는 사람은 그대로 열고, 링크를 공유하면 받은 사람도 본다. 정적 HTML 자체에는 로그인 처리가 없지만, 정적 호스팅이라 인증이 불가능한 것은 아니다. Cloudflare Access를 앞에 두거나 Worker가 정적 자산보다 먼저 요청을 검사하게 만들 수 있다. 이 사이트에는 지금 그 접근 제어를 걸지 않았다. 게다가 robots.txt와 noindex는 이를 지원하는 검색 크롤러에 대한 지시일 뿐, 규칙을 무시하는 수집기와 아카이브를 막지 못한다.
한계 자체는 처음부터 공개 범위 문서에 적혀 있었다. 문제는 「비공개 모드」라는 이름이 그 문서를 읽기 전에 결론을 내리게 만든 것이다. 이름이 문서보다 먼저 읽히고, 읽는 쪽은 그 이름을 근거로 무엇을 올릴지 정한다.
그래서 다음 날 부르는 말만 바꿨다.
| 전 | 후 |
|---|---|
SITE_PRIVATE |
SEARCH_BLOCKED |
| 비공개 모드 | 색인 차단 모드 |
| 공개 모드 | 색인 허용 모드 |
이름을 바꾼 뒤에도 _headers, robots.txt, noindex 메타는 같은 내용으로 생성됐다. 배포된 사이트는 그대로 두고, 관련 화면과 문서에서 검색 제외 지시와 접근 제한을 구분했다.
접근 제어는 가능하지만 지금은 걸지 않았다
색인 차단으로 부족하고 신원을 확인한 사람만 열게 해야 한다면 Cloudflare Access를 도메인 앞에 둘 수 있다. 현재 정책 문서가 우선 권하는 방법도 이것이다.
Worker에서 인증을 검사하는 방법도 기술적으로 가능하다. Cloudflare의 Static Assets 라우팅 문서는 run_worker_first의 용도 중 하나로 인증 검사를 든다. 실제 현재 wrangler.jsonc에는 main: "worker/index.js"가 있고, run_worker_first는 /share/*에만 걸려 있다. 이 Worker는 만료형 공유 링크를 처리할 뿐 사용자 신원을 확인하지 않는다. 나머지 사이트 요청은 Worker를 먼저 거치지 않고 정적 자산으로 응답한다.
예전 글은 Basic 인증을 붙이려고 main을 추가하면 undici 취약점이 배포 런타임에 도달한다고 적었다. 2026년 8월 6일 실제 배포 번들을 다시 연 결과는 달랐다. 번들은 worker/index.js 한 파일이었고 node_modules 참조는 없었다. 이 Worker는 workerd의 fetch를 쓰며, undici는 Wrangler와 Miniflare를 거치는 개발·빌드·배포 CLI 경로에는 남지만 배포된 Worker 런타임에는 없었다. 따라서 Worker 런타임은 생겼지만 현재 번들에 undici는 도달하지 않는다. Worker가 npm 의존성을 import하기 시작하면 그때 다시 판단해야 한다.
Basic 인증을 쓰지 않은 이유는 정적 호스팅이나 undici가 아니라 운영 방식이다. 하나의 비밀번호를 공유하면 사용자별 회수와 신원 확인이 어렵다. 접근 제어가 필요해지면 직접 만든 공용 비밀번호보다 Access 정책을 쓰기로 했다.
지금은 Access도 사이트 전체 인증 Worker도 걸지 않았다. /share/*만 별도 Worker를 거치며, 그 응답은 Worker가 직접 X-Robots-Tag를 붙인다. 대신 위치를 옮겨서 안전해지는 자료가 아니면 애초에 올리지 않는 쪽으로 정리했다. 사이트는 색인 차단 지시를 포함해 배포되어 있고 검색엔진 등록은 하지 않았다. 그래도 주소를 아는 사람과 규칙을 무시하는 수집기에는 열려 있다.
도메인을 연결하고 스위치를 넣기까지의 나흘 동안 실제로 색인된 것이 있는지는 아직 확인하지 않았다. 확인할 때는 Search Console의 Page Indexing 보고서와 URL Inspection을 먼저 본다. site: 검색은 결과가 완전하지 않다. 결과가 없다고 색인된 URL이 없다고 단정할 수 없으므로 보조 신호로만 쓴다.
함께 보면 좋은 글
이 기록과 태그가 겹치는 아티클입니다.