GEO 최적화 Guide — 전체 시리즈

  1. 1. GEO란 무엇인가 - SEO 너머의 AI 인용 전략
  2. 2. AI마다 인용하는 소스가 다르다
  3. 3. On-Site GEO 기술 구조 - 상품 DB에서 JSON-LD까지 ← 현재 글
  4. 4. Off-Site GEO - 공식 사이트를 안 보는 AI에게 선택받는 법
  5. 5. AEO - 코딩 에이전트가 읽는 문서는 왜 다른가

JSON-LD를 어디서 만들어서 어디에 넣느냐

이전 글 에서 플랫폼별 인용 소스가 다르다는 걸 확인했다. Gemini는 공식 사이트를, ChatGPT는 디렉토리를, Perplexity는 커뮤니티를 선호한다. 공통점이 하나 있다. 어떤 플랫폼이든 구조화 데이터가 있는 페이지의 인용 확률이 높다는 거다.

그래서 On-Site GEO의 기술적 핵심은 결국 이 질문으로 수렴한다. 상품 마스터 DB에 있는 데이터를 어떻게 가공해서 HTML <head>에 JSON-LD로 꽂을 것인가.

단순해 보이지만, 실제로 손대보면 얽혀 있는 게 한둘이 아니다. 상품 DB 필드명은 약어 투성이고, AI가 이해할 수 있는 속성은 DB에 없고, SPA로 만들어진 사이트는 크롤러가 JSON-LD를 못 읽는다. 이 글에서는 이 문제들을 어떤 구조로 풀 수 있는지 다룬다.

GEO 시스템의 동심원 구조

GEO 시스템은 안쪽에서 바깥으로 확장되는 4개 레이어로 이루어진다.

레이어구성요소역할
Core상품 마스터 DBSSOT(Single Source of Truth). 모든 데이터의 원천
Channel웹사이트 / 모바일 앱JSON-LD 삽입, SSR 렌더링
API상품 조회 APIAI 에이전트가 호출할 수 있는 인터페이스
AgentChatGPT / Gemini / Perplexity최종 소비자 접점

Core에서 시작해서 Channel을 거쳐 Agent까지 데이터가 흘러간다. 각 레이어를 지날 때마다 데이터의 형태가 바뀐다. DB의 raw 필드가 구조화된 JSON-LD가 되고, 그게 AI 답변의 인용 출처가 된다.

여기서 놓치기 쉬운 게 API 레이어다. JSON-LD만 잘 넣으면 되는 거 아닌가 싶지만, ChatGPT Plugins이나 MCP(Model Context Protocol) 같은 AI 에이전트 연동까지 고려하면 별도의 API 레이어가 필요하다. 지금 당장은 아니더라도 설계 단계에서 고려해두면 나중에 덜 고생한다.

데이터 파이프라인 3단계

상품 description을 통째로 관리하지 않고, 필드 단위로 분해하면 AI가 정확히 인용한다. 이게 파이프라인의 핵심 아이디어다.

1단계: DB 정제 - 기존 필드 매핑

기존 상품 마스터 DB에서 Schema.org 필드로 매핑하는 단계다. 새 데이터를 만드는 게 아니라 있는 데이터를 정리하는 거다.

DB 필드              →  Schema.org 필드
─────────────────────────────────────────
PROD_NM              →  name
BRND_CD (코드 변환)  →  brand.name
GTIN_13              →  gtin13
PRC_AMT              →  offers.price
STCK_YN              →  offers.availability
IMG_URL              →  image
CTG_NM               →  category

필드 수는 업종에 따라 15~18개 정도다. 대부분 이미 DB에 있는 값이라 개발 공수가 크지 않다. 다만 코드 값을 사람이 읽을 수 있는 값으로 변환하는 작업이 필요하다. BRND_CD = P1042brand.name = "○○식품"으로 바꿔야 AI가 이해한다.

이 단계에서 가장 많이 막히는 게 GTIN이다. GS1 표준 식별자인데, 같은 상품이라도 용량이나 맛이 다르면 GTIN이 달라야 한다. “초코스틱 오리지널"과 “초코스틱 아몬드"를 하나의 대표코드로 묶어놓으면 AI는 둘을 구분하지 못한다.

2단계: LLM 추출 - AI 기반 속성 자동 생성

DB에 없는데 AI 인용에 필요한 속성들이 있다. 타겟 사용자, 사용 상황, 감성 키워드 같은 것들이다. 이걸 사람이 일일이 쓰면 SKU가 수천 개일 때 현실적으로 불가능하다.

LLM이 기존 상품 설명, 리뷰, 카테고리 정보를 읽고 자동으로 추출하게 한다.

소스필드설명예시
DB@typeSchema.org 유형Product
DBname상품명그램 16
DBgtin13GS1 식별자8801056038800
LLMtargetUser타겟 사용자대학생, 직장인
LLMoccasion사용 상황입학 선물, 업무용
LLMsentiment감성 키워드가벼운, 세련된
LLMnutrition영양 정보무설탕
LLMsafety안전 정보CAS 9002-88-4

LLM 추출 필드는 업종마다 다르다. 식품이면 영양정보와 원재료가 핵심이고, 호텔이면 부대시설과 체크인 시간이 중요하다. 화학/B2B라면 물성 데이터와 인증 정보가 들어간다.

이 단계에서 1015개 필드가 추가된다. 1단계와 합치면 상품 하나에 2533개 필드가 구조화되는 셈이다.

3단계: JSON-LD 출력 - 자동 변환 및 SSR 배포

1단계와 2단계에서 모인 필드를 Schema.org 규격의 JSON-LD로 변환하고, SSR을 통해 HTML <head>에 자동 주입한다.

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "초코스틱 오리지널",
  "gtin13": "8801234567890",
  "brand": {
    "@type": "Brand",
    "name": "○○식품"
  },
  "description": "초콜릿 코팅된 바삭한 스틱 과자. 1봉 46g 기준 200kcal.",
  "offers": {
    "@type": "Offer",
    "price": 1500,
    "priceCurrency": "KRW",
    "availability": "https://schema.org/InStock"
  },
  "nutrition": {
    "@type": "NutritionInformation",
    "calories": "200 calories",
    "servingSize": "1봉 (46g)"
  }
}

이 JSON-LD가 <head> 태그 안에 들어가면 1편 에서 다룬 Invisible GEO 가 완성된다. 사용자 눈에는 안 보이지만 AI와 검색엔진이 파싱한다.

식품 상품의 GEO 적용 전후가 어떻게 달라지는지 데모로 확인할 수 있다.

Description 작성 4대 원칙

파이프라인에서 가장 사람 손이 많이 타는 부분이 상품 설명(description)이다. AI가 인용하기 좋은 description에는 패턴이 있다.

사실 기반 - 객관적 정보만 넣는다. “업계 최고"나 “고객 만족 1위” 같은 광고 문구는 AI가 무시한다.

100~300자 - AI가 참조하기 적절한 길이다. 너무 짧으면 맥락이 없고, 너무 길면 핵심이 묻힌다.

자연어 키워드 - Princeton/Georgia Tech 연구 에서 확인된 것처럼 키워드를 반복 삽입하면 AI 가시성이 오히려 떨어진다. 자연스러운 문장 안에 키워드를 녹여야 한다.

SKU별 고유성 - 같은 템플릿을 복사해서 상품명만 바꾸면 AI는 중복 콘텐츠로 판단한다. 상품마다 고유한 설명이 있어야 한다.

<!-- 나쁜 예: 광고 문구 + 키워드 반복 -->
<meta name="description" content="회사소개"/>

<!-- 좋은 예: 사실 기반, 자연어, 적절한 길이 -->
<meta name="description"
  content="○○케미칼은 글로벌 석유화학 전문기업으로,
  연간 매출 15조원 규모의 PE/PP 제품을
  50개국에 공급합니다. ESG 경영과
  탄소중립 2050을 선도합니다."/>

SSR이 필수인 이유

JSON-LD를 만들었어도 AI 크롤러가 못 읽으면 소용없다. 여기서 SPA(Single Page Application)가 발목을 잡는다.

SPA는 브라우저에서 JavaScript를 실행해야 콘텐츠가 렌더링된다. 사람 눈에는 정상으로 보이지만, GPTBot이나 Google-Extended 같은 AI 크롤러는 대부분 JS를 실행하지 않는다. <head>에 JSON-LD를 넣었어도 서버가 빈 HTML을 보내면 크롤러 입장에서는 없는 거나 마찬가지다.

SSR(Server-Side Rendering)로 전환하면 서버에서 완성된 HTML을 보내기 때문에 크롤러가 JS 실행 없이 JSON-LD를 바로 읽는다.

Next.js App Router 기준으로 보면 이렇다:

// app/product/[id]/page.tsx
export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id);

  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Product",
    "name": product.name,
    "gtin13": product.gtin,
    "brand": { "@type": "Brand", "name": product.brand },
    "description": product.description,
    "image": product.imageUrl,
    "offers": {
      "@type": "Offer",
      "price": product.price,
      "priceCurrency": "KRW",
      "availability": "https://schema.org/InStock",
      "url": product.pageUrl
    }
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(jsonLd)
        }}
      />
      <ProductDetail product={product} />
    </>
  );
}

서버에서 fetchProduct로 DB 데이터를 가져오고, JSON-LD 객체를 만들어서 <script> 태그로 주입한다. 이 HTML이 크롤러에게 그대로 전달된다.

SSR 도입이 부담스러운 경우, 과도기적으로 Google Tag Manager(GTM)를 활용해 JSON-LD를 주입하는 방법도 있다. 완전한 SSR보다는 효과가 떨어지지만 SPA를 당장 전환할 수 없을 때 쓸 수 있는 우회 방안이다.

SSR 적용 시 주의점

항목장점단점극복 방안
SEO 최적화크롤러가 JS 없이 즉시 인식초기 개발 비용SDK/공통 모듈 제공
데이터 반영DB 변경 시 자동 업데이트서버 부하 증가Redis 캐싱 + ISR 활용
중앙 관리전체 사이트 일괄 적용개발팀 의존도관리 콘솔에서 비개발자 관리
검증 자동화빌드 시 유효성 검증 포함레거시 시스템 전환GTM 하이브리드 병행

서버 부하는 Redis 캐싱과 ISR(Incremental Static Regeneration)로 상당 부분 해소된다. 상품 정보가 바뀌지 않는 한 캐시된 HTML을 그대로 쓰면 된다.

데이터 신선도가 인용을 좌우한다

구조화를 잘 해놔도 오래된 데이터는 밀린다.

Perplexity에서 높은 인용을 받은 페이지를 분석해보면, 4분의 3 이상이 한 달 이내에 업데이트된 것이었다. ChatGPT Shopping은 피드를 15분 간격으로 갱신한다 (OpenAI). 석 달 넘게 손 안 댄 페이지는 AI 인용 순위에서 밀려날 가능성이 높다.

신선도 관리 기준을 잡으면 이렇다:

  • 핵심 데이터 (가격, 재고, 프로모션): 24시간 이내 갱신
  • 일반 데이터 (상품 설명, 이미지): 7일 이내 갱신
  • 정적 데이터 (브랜드 정보, 회사 소개): 월 1회 점검

sitemap.xml에서 lastmod 날짜를 실제 업데이트 시점에 맞춰 갱신하고, IndexNow API로 변경 사항을 검색엔진에 즉시 알려주는 것도 효과가 있다.

// next-sitemap.config.js
module.exports = {
  siteUrl: 'https://www.example.com',
  generateRobotsTxt: true,
  changefreq: 'daily',
  transform: async (config, path) => ({
    loc: path,
    changefreq: path.includes('/product/') ? 'daily' : 'weekly',
    priority: path.includes('/product/') ? 0.9 : 0.5,
    lastmod: new Date().toISOString(),
  }),
};

검증 - 넣었으면 확인해야 한다

JSON-LD를 넣었다고 끝이 아니다. 실제로 크롤러가 잘 읽는지 확인해야 한다.

Google Rich Results Test - search.google.com/test/rich-results 에서 URL을 입력하면 구조화 데이터가 정상 인식되는지 바로 확인할 수 있다.

curl로 크롤러 시뮬레이션 - AI 크롤러의 User-Agent로 직접 요청을 보내서 JSON-LD가 HTML에 포함되어 오는지 확인한다.

# GPTBot으로 요청
curl -A "GPTBot" https://www.example.com/product/12345 | grep "application/ld+json"

# HTML 소스에서 JSON-LD 추출
curl -s https://www.example.com/product/12345 \
  | grep -oP '<script type="application/ld\+json">.*?</script>'

SPA인데 SSR 전환이 안 된 상태라면, curl 결과에 JSON-LD가 안 나올 수 있다. 이게 바로 SSR이 필수인 이유다.

폼에 상품 정보를 입력하면 JSON-LD가 자동 생성되는 빌더도 만들어두었다. 구조를 직접 만져보면 감이 잡힌다.

실무에서 자주 부딪히는 문제들을 정리하면 이렇다:

증상원인해결
JSON-LD가 크롤링 안됨robots.txt 차단GPTBot, Google-Extended Allow 설정
AI가 데이터를 인용 안함Schema.org 타입 오류Rich Results Test로 검증
API 응답 속도 느림캐싱 미적용Redis 캐싱 + 필드 최소화
SSR 전환 후 서버 부하매 요청마다 DB 조회ISR + Redis 캐싱 병행