GEO 최적화 Guide — 전체 시리즈 1. GEO란 무엇인가 - SEO 너머의 AI 인용 전략 2. AI마다 인용하는 소스가 다르다 3. On-Site GEO 기술 구조 - 상품 DB에서 JSON-LD까지 ← 현재 글 4. Off-Site GEO - 공식 사이트를 안 보는 AI에게 선택받는 법 5. AEO - 코딩 에이전트가 읽는 문서는 왜 다른가 JSON-LD를 어디서 만들어서 어디에 넣느냐 이전 글 에서 플랫폼별 인용 소스가 다르다는 걸 확인했다. Gemini는 공식 사이트를, ChatGPT는 디렉토리를, Perplexity는 커뮤니티를 선호한다. 공통점이 하나 있다. 어떤 플랫폼이든 구조화 데이터가 있는 페이지의 인용 확률이 높다는 거다. 그래서 On-Site GEO의 기술적 핵심은 결국 이 질문으로 수렴한다. 상품 마스터 DB에 있는 데이터를 어떻게 가공해서 HTML 에 JSON-LD로 꽂을 것인가. 단순해 보이지만, 실제로 손대보면 얽혀 있는 게 한둘이 아니다. 상품 DB 필드명은 약어 투성이고, AI가 이해할 수 있는 속성은 DB에 없고, SPA로 만들어진 사이트는 크롤러가 JSON-LD를 못 읽는다. 이 글에서는 이 문제들을 어떤 구조로 풀 수 있는지 다룬다. GEO 시스템의 동심원 구조 GEO 시스템은 안쪽에서 바깥으로 확장되는 4개 레이어로 이루어진다. 레이어 구성요소 역할 Core 상품 마스터 DB SSOT(Single Source of Truth). 모든 데이터의 원천 Channel 웹사이트 / 모바일 앱 JSON-LD 삽입, SSR 렌더링 API 상품 조회 API AI 에이전트가 호출할 수 있는 인터페이스 Agent ChatGPT / 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 = P1042를 brand.name = "○○식품"으로 바꿔야 AI가 이해한다. 이 단계에서 가장 많이 막히는 게 GTIN이다. GS1 표준 식별자인데, 같은 상품이라도 용량이나 맛이 다르면 GTIN이 달라야 한다. “초코스틱 오리지널"과 “초코스틱 아몬드"를 하나의 대표코드로 묶어놓으면 AI는 둘을 구분하지 못한다. 2단계: LLM 추출 - AI 기반 속성 자동 생성 DB에 없는데 AI 인용에 필요한 속성들이 있다. 타겟 사용자, 사용 상황, 감성 키워드 같은 것들이다. 이걸 사람이 일일이 쓰면 SKU가 수천 개일 때 현실적으로 불가능하다. LLM이 기존 상품 설명, 리뷰, 카테고리 정보를 읽고 자동으로 추출하게 한다. 소스 필드 설명 예시 DB @type Schema.org 유형 Product DB name 상품명 그램 16 DB gtin13 GS1 식별자 8801056038800 LLM targetUser 타겟 사용자 대학생, 직장인 LLM occasion 사용 상황 입학 선물, 업무용 LLM sentiment 감성 키워드 가벼운, 세련된 LLM nutrition 영양 정보 무설탕 LLM safety 안전 정보 CAS 9002-88-4 LLM 추출 필드는 업종마다 다르다. 식품이면 영양정보와 원재료가 핵심이고, 호텔이면 부대시설과 체크인 시간이 중요하다. 화학/B2B라면 물성 데이터와 인증 정보가 들어간다. 이 단계에서 1015개 필드가 추가된다. 1단계와 합치면 상품 하나에 2533개 필드가 구조화되는 셈이다. 3단계: JSON-LD 출력 - 자동 변환 및 SSR 배포 1단계와 2단계에서 모인 필드를 Schema.org 규격의 JSON-LD로 변환하고, SSR을 통해 HTML 에 자동 주입한다. { "@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가 태그 안에 들어가면 1편 에서 다룬 Invisible GEO 가 완성된다. 사용자 눈에는 안 보이지만 AI와 검색엔진이 파싱한다. 식품 상품의 GEO 적용 전후가 어떻게 달라지는지 데모로 확인할 수 있다. Demo - JSON-LD Before/After 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를 실행하지 않는다. 에 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 객체를 만들어서 ' SPA인데 SSR 전환이 안 된 상태라면, curl 결과에 JSON-LD가 안 나올 수 있다. 이게 바로 SSR이 필수인 이유다. 폼에 상품 정보를 입력하면 JSON-LD가 자동 생성되는 빌더도 만들어두었다. 구조를 직접 만져보면 감이 잡힌다. Demo - JSON-LD 빌더 실무에서 자주 부딪히는 문제들을 정리하면 이렇다: 증상 원인 해결 JSON-LD가 크롤링 안됨 robots.txt 차단 GPTBot, Google-Extended Allow 설정 AI가 데이터를 인용 안함 Schema.org 타입 오류 Rich Results Test로 검증 API 응답 속도 느림 캐싱 미적용 Redis 캐싱 + 필드 최소화 SSR 전환 후 서버 부하 매 요청마다 DB 조회 ISR + Redis 캐싱 병행