GPTBot·CCBot·ClaudeBot·PerplexityBot 등 주요 AI 크롤러는 JavaScript를 실행하지 않고 초기 HTTP 응답 HTML만 파싱한다. 클라이언트 사이드 렌더링(CSR) SPA가 서버에서 <div id="root"></div>만 반환하면 AI 크롤러 시점에 본문이 없는 것과 같다. 서버 사이드 렌더링(SSR)은 요청 시 완성된 HTML을 반환해 이 접근성 문제를 해소하지만, 이것이 AI 답변 인용으로 자동 이어지는 충분조건은 아니다. AI 검색의 RAG 파이프라인에서 페이지가 retrieval 풀에 포함되려면 접근성 외에 구조화 데이터·콘텐츠 밀도·도메인 신호가 함께 갖춰져야 한다.
AI 크롤러의 HTML 파싱 메커니즘
- HTTP GET 후 원시 HTML 직접 파싱: 크롤러는 헤드리스 브라우저 없이 서버 응답 바디를 그대로 파싱한다. 왜: 헤드리스 브라우저 운영 비용 없이 수십억 페이지를 크롤해야 하기 때문이다. 어떻게:
curl -A "GPTBot/1.0" https://example.com/article명령으로 크롤러 시점 HTML을 재현하고 본문 유무를 확인한다. - JSON-LD·메타 태그 직접 추출:
<head>의<meta name="description">과<script type="application/ld+json">은 JS 없이 파싱된다. 왜: RAG 파이프라인이 페이지를 벡터 인덱스에 등록할 때 구조화 메타데이터를 함께 저장하기 때문이다. 어떻게: SSR 서버 컴포넌트에서 JSON-LD를 서버 측에서 직렬화해 초기 HTML에 포함시킨다. - robots.txt Disallow 즉시 준수: AI 크롤러는 fetch 전 robots.txt를 읽는다. 왜: GPTBot 등 공식 크롤러는 Disallow 경로를 학습 데이터 및 RAG 풀에서 제외하도록 정책화돼 있다. 어떻게: User-agent별로 경로를 세분화해 AI 학습 허용과 실시간 RAG 검색 허용을 독립적으로 제어한다.
렌더링 방식별 AI 색인 가용성 비교
| 항목 | CSR (SPA) | SSR | SSG / ISR |
|---|---|---|---|
| 초기 HTML 텍스트 밀도 | 거의 없음 | 완전 | 완전 |
| JSON-LD 파싱 보장 | 불확실 (JS 의존) | 보장 | 보장 |
| AI 학습 크롤 적합성 | 불리 | 유리 | 유리 (갱신 지연 주의) |
| 실시간 RAG 검색 적합성 | 불리 | 유리 | revalidate 주기 의존 |
| 구현 복잡도 | 낮음 | 높음 | 중간 |
구현: robots.txt AI 크롤러 세분화 및 캐시 헤더
robots.txt — AI 크롤러별 경로 분리
단일 User-agent: *로 모든 크롤러를 일괄 제어하면 AI 학습 차단과 실시간 RAG 허용을 구분할 수 없다. 아래와 같이 크롤러 계열별로 분리 선언한다.
# /public/robots.txt
# AI 학습 데이터 수집 크롤러
User-agent: GPTBot
Allow: /blog/
Allow: /docs/
Disallow: /admin/
Disallow: /user/
Disallow: /api/
User-agent: CCBot
Allow: /blog/
Disallow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
# 실시간 RAG 검색 크롤러 (전면 허용 권장)
User-agent: PerplexityBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
# 크롤 빈도 힌트 (지원 크롤러 한정)
Crawl-delay: 10
Sitemap: https://example.com/sitemap.xml
Next.js — Cache-Control 헤더 및 JSON-LD 서버 삽입
SSR 페이지의 캐시 정책이 잘못 설정되면 크롤러가 구버전 HTML을 반복 수집한다. 아래는 Next.js App Router에서 블로그 경로에 캐시 헤더를 설정하고 JSON-LD를 서버 컴포넌트에서 직렬화하는 패턴이다.
// next.config.js — AI 크롤러용 캐시 헤더
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/blog/:slug*',
headers: [
{
key: 'Cache-Control',
// 1시간 서버 캐시, 하루 stale-while-revalidate
value: 'public, s-maxage=3600, stale-while-revalidate=86400',
},
{ key: 'X-Robots-Tag', value: 'index, follow' },
],
},
];
},
};
module.exports = nextConfig;
// app/blog/[slug]/page.tsx (서버 컴포넌트 — JS 없이 HTML에 직렬화)
export default async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 3600 },
}).then((r) => r.json());
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.description,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: { '@type': 'Person', name: post.author },
};
// 서버에서 직렬화 — 크롤러는 이 JSON-LD를 초기 HTML에서 바로 읽는다
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{post.content}</article>
</>
);
}
검증 및 측정
- curl 크롤러 시뮬레이션:
curl -A "GPTBot/1.0" -s https://example.com/blog/slug | grep -c "<p>"로 본문 단락 수를 측정한다. 왜: JS 없이 크롤러가 실제로 보는 HTML 상태를 가장 직접적으로 재현할 수 있다. 어떻게: CI 파이프라인에 이 명령을 포함해 0이 반환되면 빌드 실패 처리한다. - 서버 액세스 로그 AI 크롤러 추출:
grep -E "GPTBot|CCBot|ClaudeBot|PerplexityBot" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20으로 실제 크롤된 경로와 빈도를 확인한다. 왜: robots.txt Allow 경로가 실제로 크롤되는지 증거 기반으로 파악할 수 있다. - Google Rich Results Test: URL을 입력해 서버 HTML에 JSON-LD가 올바르게 포함됐는지 검사한다. 왜: Google의 렌더링 파이프라인이 구조화 데이터를 인식하면 AI Overviews 인용 가능성이 높아진다. 어떻게: 배포 체크리스트에 신규 페이지마다 이 테스트를 의무화한다.
- llms.txt 게시:
/llms.txt에 LLM이 참조해야 할 핵심 페이지 목록과 설명을 마크다운으로 기술한다. 왜: Perplexity 등 일부 AI 시스템이 크롤 시 이 파일을 내비게이션 힌트로 사용한다(사실상 표준 형성 중이며, 현재 지원 여부는 플랫폼별로 다름). 어떻게:# Site Title헤더 아래- /docs/api : API 참조 문서형식으로 작성하고Sitemap과 함께 유지한다.
흔한 오해 — "SSR로 전환하면 AI 인용이 즉시 증가한다"
SSR은 AI 크롤러가 페이지 텍스트에 접근할 수 있는 필요조건이지 충분조건이 아니다. RAG retrieval은 텍스트 접근성 외에 쿼리-콘텐츠 임베딩 유사도, 도메인 권위도, datePublished 신선도, FAQPage·HowTo 스키마 유무를 복합적으로 평가한다. SSR 전환 후에도 인용률이 변하지 않는다면 콘텐츠 구조화 데이터를 점검해야 한다. 올바른 처리법은 SSR 전환과 동시에 핵심 페이지에 Article·FAQPage Schema.org 타입을 서버 컴포넌트에서 직접 삽입하고, curl 시뮬레이션으로 구조화 데이터가 초기 HTML에 포함됐는지 배포마다 검증하는 것이다.
기술적 FAQ
Next.js ISR(Incremental Static Regeneration)은 AI 크롤러에게 SSR과 동일하게 작동하나요?
revalidate 주기 이내라면 CDN이 캐시된 완성 HTML을 반환하므로 크롤러 입장에서는 SSG와 동일하다. 문제는 revalidate가 만료되기 전에 크롤러가 방문할 경우 구버전 HTML을 수집한다는 점이다. 콘텐츠 갱신이 잦은 페이지(뉴스·실시간 데이터)는 ISR 대신 cache: 'no-store' SSR을 적용하고, Cache-Control: s-maxage=3600, stale-while-revalidate=86400 헤더로 크롤러에게 신선도를 명시하는 것이 권장된다.
React Server Components(RSC)만으로 구성한 페이지도 CSR처럼 AI 색인에서 불리한가요?
RSC는 서버에서 HTML 스트림으로 직렬화돼 응답하므로 초기 HTML에 콘텐츠가 포함되며 CSR과 다르다. 단, 'use client' 컴포넌트가 마운트 후 별도 fetch로 채우는 동적 구역(댓글·사용자 피드·개인화 영역)은 초기 HTML에 없으므로 AI 색인에서 누락된다. 해당 데이터가 AI 색인에 중요하다면 서버 컴포넌트로 리팩토링하거나, Suspense fallback 대신 서버에서 직접 데이터를 전달하는 방식으로 변경해야 한다.
참고 자료
이 글의 권고는 아래 공식 문서·연구를 근거로 합니다.