LLM이 인용하기 최적화된 126페이지 규모의 사이트를 구축했습니다. 제가 구현한 모든 것을 공개합니다.
요약
LLM이 정보를 정확하게 추출하고 인용할 수 있도록 최적화된 126페이지 규모의 Astro 기반 정적 사이트 구축 과정을 공유합니다. 검색 엔진 최적화(SEO)와 달리 LLM의 '추출 가능성(Extractability)'을 높이는 구조적 접근 방식을 다룹니다.
핵심 포인트
- LLM 검색은 문서 순위보다 정보의 추출 가능성이 핵심임
- Astro와 Cloudflare를 활용한 고성능 정적 사이트 구축
- 주제 기둥, 용어 사전, FAQ를 통한 구조적 콘텐츠 설계
- 권위가 낮은 신규 도메인이 LLM 인용을 선점하기 위한 전략
단 하나의 질문을 중심으로 구축된 Astro 기반의 정적(Static) 사이트 — 언어 모델(Language Model)이 페이지를 정확하게 인용하려면 어떤 모습이어야 하는가? 확실히 작동하는 부분과 여전히 추측에 의존하는 부분을 분리하여 정리했습니다.
최근 저는 자가 출판된 도서 시리즈를 위한 웹사이트를 구축했습니다. 작은 니치(Niche) 시장, 알려지지 않은 저자, 기존 관객 없음, 백링크(Backlink) 없음, 예산 없음. 보통이라면 무엇인가로 순위를 올리는 데 2년이 걸리거나 아예 순위권에 들지 못할 법한 종류의 사이트입니다.
그래서 저는 다른 표면(Surface)을 목표로 구축했습니다. "특정 키워드로 4위에 오르기"가 아니라, "누군가 질문을 던졌을 때 ChatGPT가 인용하는 대상이 되기"를 목표로 삼았습니다.
이 포스트는 전체적인 접근 방식을 다룹니다: 126페이지의 분량, 제가 실제로 구현(Shipped)한 것들, 그리고 — 더 유용하게는 — 확실히 작동한다고 알고 있는 부분과 여전히 교육된 추측(Educated guess)에 불과한 부분 사이의 솔직한 구분입니다. 아직 결과는 없습니다. 사이트를 만든 지 몇 달이 아니라 며칠밖에 되지 않았으며, Bing은 현재 이 사이트를 "발견되었으나 크롤링되지 않음(discovered but not crawled)"으로 표시하고 있습니다. 이는 인바운드 링크(Inbound links)가 전혀 없는 도메인에서 예상할 수 있는 정확한 상태입니다. 따라서 이 글을 사례 연구(Case study)가 아닌 구축 로그(Build log)로 취급해 주세요.
6개월 후에 승전보를 울리기보다는, 지금 방법을 게시하고 제가 무엇을 잘못했는지 지적받는 편을 택하겠습니다.
전제 (The premise)
검색(Search)과 LLM 검색(Retrieval)은 서로 다른 것을 보상합니다.
검색 엔진은 문서를 순위 매깁니다. 사용자에게 10개의 링크를 제공하고 사용자가 결정하게 만들고 싶어 합니다. 권위(Authority), 링크, 최신성(Freshness)이 지배적이며, 이것이 새로운 도메인이 정체되는 이유입니다. 부족한 신호들이 바로 가장 중요한 신호들이기 때문입니다.
질문에 답하는 언어 모델은 추출(Extraction)에 더 가까운 작업을 수행합니다. 모델은 질문에 깔끔하게 답하면서, 형태를 망가뜨리지 않고 가져올 수 있으며, 모델이 인식하는 엔티티(Entity)에 귀속시킬 수 있는 구절(Passage)이 필요합니다. 권위는 여전히 중요하지만, _추출 가능성(Extractability)_은 실질적이고 별개인 축이며, 이는 첫날부터 완전히 제어할 수 있는 요소입니다.
그것이 저의 베팅입니다. 구조가 권위를 이긴다는 것이 아니라, 구조가 새로운 사이트가 실제로 경쟁할 수 있는 부분이라는 것입니다.
스택, 간략히 (Stack, briefly)
Astro를 정적 모드 (static mode)로 사용하며, Cloudflare Workers의 정적 자산 (static assets)에 배포했습니다. 콘텐츠는 타입이 지정된 프론트매터 스키마 (typed frontmatter schemas)를 갖춘 Astro의 콘텐츠 컬렉션 (content collections)을 통해 마크다운 (markdown) 형식으로 관리됩니다.
총 126페이지이며, 모두 빌드 타임 (build time)에 사전 렌더링 (pre-rendered)됩니다. 단 하나의 동적 엔드포인트 (/api/geo, Amazon 스토어프론트를 사전 선택함)를 제외한 모든 것은 Cloudflare의 엣지 (edge)에 있는 파일입니다.
콘텐츠는 다음과 같이 나뉩니다:
- 4가지 주제 기둥 (topic pillars)에 걸친 42개의 학습 (Learn) 문서
- 용어당 하나씩 구성된 60개의 용어 사전 (glossary) 항목
- 5개의 FAQ 페이지
- 19개의 허브 (hub), 북 (book) 및 유틸리티 (utility) 페이지
콘텐츠 페이지의 총 자바스크립트 (JavaScript) 양은 거의 제로에 가깝습니다. 내비게이션 메뉴, FAQ 아코디언 (accordions), 용어 사전 검색은 모두 <details>/<summary>를 기반으로 구축되었거나 일반적인 가시적 콘텐츠로 대체(degrade)됩니다. 이는 들리는 것보다 훨씬 더 중요합니다 — "지루한 것들 (boring things)" 섹션을 참조하세요.
파트 1: 확실히 작동하는 지루한 것들 (the boring things that definitely work)
여기서 새로운 것은 아무것도 없습니다. 그저 모든 것을 제대로 수행했을 뿐이며, 그것이 핵심입니다.
서버 렌더링된 HTML (Server-rendered HTML)
일부 크롤러 (crawlers)는 자바스크립트를 실행합니다. 하지만 많은 크롤러가 이를 실행하지 않거나, 지연된 두 번째 패스 (pass)에서 실행하거나, 실행 예산을 엄격하게 제한합니다. 정적 빌드 (static build)는 이 문제를 완전히 우회합니다. 도착하는 바이트 (bytes) 안에 콘텐츠가 포함되어 있기 때문입니다.
이 포스트에서 한 가지만 얻어 가신다면, 바로 이것을 가져가십시오. 이는 매력적이지 않지만, 다른 모든 것을 압도합니다.
페이지당 하나의 질문, 첫 번째 단락에서 답변
모든 학습 (Learn) 및 FAQ 페이지는 하나의 질문으로 구성됩니다. 제목이 질문입니다. H1 태그가 질문입니다. H1 다음에 오는 첫 번째 내용은 주변 맥락 없이도 단독으로 존재할 수 있는 40~70단어 분량의 직접적인 답변입니다.
---
question: "실제로 투자란 무엇인가요?"
pillar: "investing"
...
answer 필드는 콘텐츠 스키마 (content schema)에 의해 강제되므로, 답변이 없는 페이지는 배포될 수 없습니다. 그 후 기사는 아래에서 심도 있게 다뤄집니다.
이러한 독립적인 구조를 유지하는 것이 바로 규율 (discipline)입니다. "위에서 보았듯이, 이것은 ...를 의미합니다"와 같은 표현은 맥락에서 분리되었을 때 아무런 쓸모가 없습니다. 모든 답변은 주변에 아무것도 없는 상태로 인용되더라도 살아남아야 합니다.
스키마의 더미가 아닌, 엔티티 그래프 (An entity graph, not a pile of schema)
이 부분은 제가 가장 자주 잘못 구현된 것을 목격하는 대목입니다. 대부분의 사이트는 서로 연결되지 않은 JSON-LD 덩어리들을 내보냅니다. 여기에는 Article이 있고, 저기에는 Organization이 있지만, 이들 사이의 관계는 없습니다.
대신, 모든 페이지는 노드들이 @id를 통해 서로를 참조하는 하나의 @graph를 내보냅니다:
{
"@context": "https://schema.org",
"@graph": [
...
Person과 WebSite 노드는 모든 페이지의 단일 컴포넌트에 의해 생성되므로, 엔티티(entity) 정보는 사이트 전체에서 바이트 단위로 동일합니다. 각 페이지는 그 위에 자신만의 노드를 추가합니다. 설명이 존재하는 곳은 단 한 군데뿐이므로, 한 페이지가 다른 페이지와 저자에 대해 다르게 설명할 방법은 없습니다.
sameAs는 전체 그래프에서 가장 가치가 높은 줄입니다. 이것은 검색 엔진에 한 플랫폼의 프로필과 다른 플랫폼의 프로필이 세 개의 우연한 일치가 아니라 하나의 엔티티임을 알려주는 역할을 합니다.
실제 텍스트를 반영하는 내부 링크 (Internal links that reflect the actual text)
60개의 용어 사전(glossary) 페이지는 원래 용어 사전 인덱스에서만 접근할 수 있었습니다. 이를
빌드된 사이트맵(sitemap)을 읽고 배포 시 Bing, Yandex, Seznam에 핑(ping)을 보내는 68줄짜리 스크립트입니다. 검증은 사이트 루트(root)에 있는 귀하의 키를 포함한 단일 정적 파일로 이루어집니다. 그게 전부입니다. 대시보드도, 등록 절차도 필요 없습니다.
Google에는 이에 상응하는 기능이 없지만, Bing의 인덱스 피드(index feeds)는 ChatGPT와 Copilot에 공급되므로, 20분의 시간을 투자할 가치가 매우 높습니다.
파트 2: 베팅 (the bets)
이 부분들은 증거로 정당화할 수 없습니다. 비용이 거의 제로에 가깝고 비대칭성(asymmetry)이 좋기 때문에 구현했습니다. 적절한 회의론을 유지하십시오. 저 또한 그렇습니다.
모든 기사(Article) 노드에 abstract 포함
가시적인 HTML에 나타나는 것과 동일한 40~70단어 분량의 답변이 스키마(schema)의 abstract 필드로도 들어갑니다.
솔직한 평가: 어떤 모델이 특히 abstract를 읽는다는 증거는 없습니다. 이를 구현하는 논거는 컴포넌트(component) 내의 코드 한 줄이라는 미미한 비용이 들고, 해당 필드가 이미 가시적인 텍스트에 존재하므로 클로킹(cloaking) 위험이 없으며 동기화할 것도 없다는 점입니다. 만약 아무런 효과가 없더라도, 비용은 들지 않았습니다.
llms.txt
사이트 루트에 위치하며, 엔티티(entity)에 대한 사실 관계, 각 섹션의 용도, 그리고 정규 URL(canonical URLs)을 명시한 마크다운(markdown) 파일입니다.
솔직한 평가: 이것은 제안된 관례(convention)이지 표준(standard)은 아닙니다. 현재 주요 크롤러(crawlers)들이 이를 소비한다는 증거는 알지 못합니다. 파일 하나면 충분합니다. 진정한 부수적 이점은 이를 작성함으로써 귀하의 사이트가 실제로 무엇을 주장하는지를 단 한 페이지에 명시하게 된다는 점입니다. 저의 경우, 이 과정에서 제 카피(copy) 내의 두 가지 불일치를 발견했습니다.
robots.txt 내의 콘텐츠 신호(Content Signals)
이것은 일반적인 조언을 의도적으로 뒤집은 것입니다:
User-agent: *
Content-Signal: ai-train=yes, search=yes, ai-input=yes
Allow: /
여기에 GPTBot, ClaudeBot, PerplexityBot, CCBot, Google-Extended 등을 위한 명시적인 Allow 블록을 추가했습니다.
대부분의 사이트는 이들을 차단하는 데 급급합니다. 보호해야 할 트래픽이 있는 기성 퍼블리셔(Publisher)에게 차단은 일관성 있는 입장입니다. 하지만 인지도가 낮은 저자에게는 AI 답변에서 제외되는 것이 AI 답변에 포함되는 것보다 훨씬 더 큰 위험입니다. 당신이 누구냐에 따라 계산법은 진정으로 달라지며, 저는 많은 사람들이 이러한 산술적 계산을 하지 않은 채 기본 설정을 그대로 복제하고 있다고 생각합니다.
이 경로를 선택할 때 알아두어야 할 두 가지 사항:
Edge rules(에지 규칙)는 robots.txt가 호출되기 _전_에 적용됩니다. 사용 중인 CDN의 봇 보호(bot-protection) 설정이 당신이 작성한 모든 Allow: 라인을 조용히 무시할 수 있습니다. 당신의 리포지토리(repo)는 한 가지를 말하고 에지(edge)는 다른 것을 수행하며, 당신은 그 사실을 전혀 모를 수 있습니다.
또한 일부 CDN은 당신을 대신해 robots.txt를 관리합니다. 제공되는 파일에 # BEGIN Cloudflare Managed content가 나타난다면, 그것은 더 이상 당신의 것이 아닙니다. 당신이 배포했다고 생각하는 것이 아니라, 실제로 제공되는 파일과 리포지토리에 있는 파일을 비교(diff)해 보세요.
내 시간을 낭비하게 만든 Cloudflare의 두 가지 함정
두 가지 모두 Workers 정적 자산(static assets)에 특화된 문제이며, 문서만 봐서는 전혀 알 수 없습니다.
Worker 코드 내의 리다이렉트(Redirects)는 존재하는 파일에 대해서는 절대 실행되지 않습니다. 정적 자산과 일치하는 요청은 스크립트를 전혀 호출하지 않고 에지에서 바로 제공됩니다. 저는 Worker에 www → apex 리다이렉트를 작성했지만, 배포한 순간부터 그것은 죽은 코드(dead code)였습니다. 파일로 해석되지 않는 URL에 대해서만 실행되었는데, 이는 실제 페이지의 경우 결코 일어날 수 없는 일입니다. 호스트 수준의 리다이렉트는 파이프라인의 더 앞 단계에서 실행되는 리다이렉트 규칙(Redirect Rule)에 포함되어야 합니다.
와일드카드 경로(wildcard route)가 서브도메인을 잡아먹을 수 있습니다. 이 문제로 하루를 통째로 날렸습니다. Worker에 *.example.com/*를 설정해 두었습니다. 나중에 media.example.com 뒤에 R2를 사용하여 60개의 비디오 파일을 두었습니다. 그런데 모든 파일이 404를 반환했습니다.
버킷은 정상이었고, 파일도 정상이었으며, 콘텐츠 타입(content types)과 크기도 정확했습니다. 커스텀 도메인도 '활성(Active)' 상태로 표시되었습니다. 문제는 Worker 경로가 R2 커스텀 도메인보다 우선순위를 갖는다는 점이었습니다. 그래서 Worker가 응답을 가로챈 뒤 일치하는 파일을 찾지 못했고, 결과적으로 _웹사이트 자체의 404 페이지_를 제공하고 있었던 것입니다.
결정적인 단서는 해당 URL이 사이트와 아무런 관련이 없음에도 불구하고, 나타난 404 페이지가 나의 스타일과 나의 문구로 구성되어 있었다는 점이었습니다. 만약 HTML을 제공해서는 안 되는 서브도메인에서 본인의 에러 페이지를 보게 된다면, 다른 무엇보다 먼저 Worker 라우트(routes)를 확인하십시오. 저는 완벽하게 작동하고 있던 서비스의 디버깅에 수 시간을 허비했습니다.
제가 권장하는 조치 사항
새로운 사이트를 운영 중이며 권위(authority)가 없는 상태라면, 다음 순서대로 진행하십시오:
- 서버 사이드 렌더링 (Server-rendered) HTML을 배포하십시오. 그 외의 모든 것은 이 작업에 비하면 사소한 오차 수준입니다.
- 페이지당 하나의 질문을 던지고, 첫 70단어 이내에 답변을 포함하며, 내용이 그 자체로 완결성을 갖추게 하십시오.
@id참조를 포함한 하나의 엔티티 그래프 (entity graph)를 구축하십시오. 이는 하나의 컴포넌트에서 생성되어 어디에서나 동일해야 합니다.sameAs를 채우십시오. 이는 사용 가능한 가장 저렴하면서도 강력한 신호입니다.- 키워드 맵이 아니라, 실제 텍스트 내용에 기반하여 내부 링크를 연결하십시오.
- 그다음에 추측에 기반한 작업들을 고민하십시오.
1번부터 5번까지의 항목은 그저 기본을 제대로 수행하는 것입니다. 만약 LLM 추출(LLM-extraction) 가설이 과장된 것으로 밝혀지더라도, 이 작업은 일반적인 검색을 통해 여전히 보상을 받을 것입니다. 이것이 설령 전제가 틀리더라도 제가 이 접근 방식을 옹호하는 주요 이유입니다. 최악의 경우라 할지라도 "구조가 잘 잡힌 사이트를 구축했다"는 결과가 남기 때문입니다.
제가 모르는 것들
2부의 내용이 실제로 효과가 있을지 여부. 질문 우선 구조(question-first structure)가 단순히 좋은 페이지인 경우와 비교했을 때 인용률을 측정 가능할 정도로 개선하는지 여부. llms.txt가 1년 뒤에 표준이 될지 아니면 각주로 남을지 여부입니다.
수치가 확보되는 대로 게시하겠습니다. 만약 이 중 어떤 것이든 제대로 측정해 보셨다면 진심으로 의견을 듣고 싶습니다. 특히 구조적인 작업이 아무런 차이를 만들지 못하며, 결국 모든 것이 권위(authority)의 문제라는 것을 발견하셨다면 더욱 그렇습니다.
구현 내용을 확인하고 싶으시다면 사이트는 edmundwarde.com입니다. 어떤 Learn 페이지든 소스 보기를 해보시면 그래프가 모두 구현되어 있는 것을 볼 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기