seo

포케코리아 SEO 개선기 2편 — JSON-LD 구조화 데이터로 검색 노출 강화하기

퀴즈 페이지 5개의 BreadcrumbList 오류 수정, ItemList·HowTo JSON-LD 추가, 메타데이터 상수화, thin content 해소까지 12가지 SEO 개선을 체계적으로 적용한 과정을 정리합니다.

seo json-ld structured-data nextjs

들어가며

이 글은 포케코리아 SEO 개선기 시리즈의 두 번째 글입니다.

1편 Path URL 전환 → 2편 JSON-LD 구조화 데이터 → 3편 기술 도감 URL 마이그레이션

포케코리아에는 실루엣 퀴즈, 특성 퀴즈, 타입 퀴즈, 타입 상성 퀴즈 4종류의 퀴즈가 있다. 메인 퀴즈 페이지(/quiz)와 개별 퀴즈 페이지 4개, 총 5개 페이지의 SEO를 점검한 결과 여러 문제가 발견되었다.

외부 SEO 리뷰 피드백도 반영하여 총 12가지 변경을 진행했다.

JSON-LD 구조화 데이터란

JSON-LD(JavaScript Object Notation for Linked Data)는 Google이 공식 권장하는 구조화 데이터 형식이다. HTML의 <script> 태그 안에 작성하여 검색엔진에게 페이지의 의미를 전달한다.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "포켓몬 퀴즈",
  "url": "https://poke-korea.com/quiz"
}
</script>

Microdata나 RDFa와 달리 HTML 본문과 분리되어 있어 유지보수가 쉽고, 동적으로 생성하기도 편하다. Google은 올바른 구조화 데이터를 적용하면 리치 결과(Rich Results)로 표시할 수 있다고 안내한다.

변경 1~4: JSON-LD 오류 수정 및 보강

메인 퀴즈 페이지의 BreadcrumbList에 잘못된 name이 들어가 있었다.

// Before — "타입 상성 계산기"라는 다른 페이지의 이름이 들어가 있었음
{ position: 2, name: '타입 상성 계산기', item: 'https://poke-korea.com/quiz' }

// After
{ position: 2, name: '포켓몬 퀴즈', item: 'https://poke-korea.com/quiz' }

BreadcrumbList는 검색 결과에서 URL 대신 탐색 경로로 표시되기 때문에, name이 실제 페이지와 일치하지 않으면 사용자에게 혼란을 준다.

ItemList 추가

메인 퀴즈 페이지에 4가지 퀴즈 종류를 ItemList로 구조화했다.

const QUIZ_ITEMLIST_JSON_LD = {
  '@context': 'https://schema.org',
  '@type': 'ItemList',
  name: '포켓몬 퀴즈 목록',
  numberOfItems: 4,
  itemListElement: [
    { position: 1, name: '포켓몬 실루엣 퀴즈', url: '...' },
    { position: 2, name: '포켓몬 특성 퀴즈', url: '...' },
    { position: 3, name: '포켓몬 타입 퀴즈', url: '...' },
    { position: 4, name: '포켓몬 타입 상성 퀴즈', url: '...' },
  ],
};

ItemList를 추가하면 Google이 퀴즈 종류 간의 관계를 이해하고, 캐러셀 형태의 리치 결과로 노출할 가능성이 생긴다.

primaryImageOfPage 추가

5개 퀴즈 JSON-LD 모두에 primaryImageOfPage 필드를 추가했다. 기존에 단순 문자열이었던 image 필드를 ImageObject 구조로 교체했다.

primaryImageOfPage: {
  '@type': 'ImageObject',
  url: 'https://poke-korea.com/assets/image/ogImage.png',
  width: 1200,
  height: 630,
},

OG 이미지와 동일한 이미지를 지정하여 일관성을 유지했다. width, height를 명시하면 Google 이미지 검색 최적화에도 도움이 된다.

개별 퀴즈 url 필드 추가

4개 개별 퀴즈 JSON-LD에 각 페이지의 url 필드가 빠져 있었다. 메인 퀴즈에만 있던 것을 모두 추가했다.

변경 5~6: 메타데이터 상수화와 재작성

인라인 메타데이터를 상수로 추출

각 page.tsx에 약 30줄씩 인라인으로 작성되어 있던 메타데이터를 seoMetaData.ts로 이관했다. createQuizMetadata 헬퍼 함수로 공통 구조를 생성하고, 각 페이지에서는 상수를 import만 하면 된다.

// Before — 각 page.tsx에 ~30줄
export const metadata: Metadata = {
  title: '...',
  description: '...',
  robots: getRobotsConfig(),
  openGraph: { ... },
  alternates: { ... },
  twitter: { ... },
};

// After — 각 page.tsx에 1줄
import { QUIZ_MAIN_META } from '~/constants/seoMetaData';
export const metadata = QUIZ_MAIN_META;

5개 페이지에서 약 150줄의 중복 코드가 제거되었다.

Title과 Description 재작성

외부 SEO 리뷰에서 가장 우선순위가 높았던 피드백이 Title과 Description의 구체성 부족이었다.

// Before
title: '포켓몬 퀴즈 | 포케 코리아'
description: '다양한 포켓몬 퀴즈를 통해 여러분의 포켓몬 지식을 테스트해보세요!'

// After
title: '포켓몬 퀴즈 모음 (실루엣·특성·타입·상성) | 포케 코리아'
description: '실루엣, 특성, 타입, 타입 상성까지 4종류의 포켓몬 퀴즈를 20문제 4지선다로 풀어보세요. 완료 후 결과를 바로 확인할 수 있습니다.'

개별 퀴즈도 문제 수(20문제)와 형식(4지선다)을 Title에 명시했다. JSON-LD의 name, description도 메타데이터와 일치하도록 동기화했다.

변경 7: 앵커 텍스트 개선

퀴즈 메인 페이지의 카드 링크가 모두 동일한 “시작하기 →“를 사용하고 있었다. Google SEO 가이드에서는 설명적인 앵커 텍스트를 권장한다.

// Before — 모든 퀴즈 카드에서 동일
<span>시작하기 →</span>

// After — 퀴즈별 구체적 텍스트 + aria-label
<Link aria-label={`${quiz.title} 시작하기`}>
  <span>{quiz.title} 시작하기 <span aria-hidden="true">→</span></span>
</Link>

“시작하기”에서 “포켓몬 실루엣 퀴즈 시작하기”로 변경하여 검색엔진이 링크 대상 페이지의 내용을 파악할 수 있게 했다. aria-label도 추가하여 접근성도 함께 개선했다.

변경 8~9: Thin Content 해소

문제: 텍스트가 부족한 페이지

Thin content는 사용자에게 실질적인 가치를 제공하지 못하는 빈약한 콘텐츠를 말한다. Google의 Helpful Content System은 이런 페이지가 많은 사이트의 전체 순위를 하락시킬 수 있다.

퀴즈 페이지들이 이에 해당했다. 메인 퀴즈 페이지는 약 200자, 개별 퀴즈의 BeforeStage는 약 80자(4줄 리스트)에 불과했다.

해결: 설명 콘텐츠 블록 추가

각 퀴즈별 SEO 텍스트 콘텐츠(300~500자)를 quiz.constants.ts에 상수로 정의하고, 페이지에 설명 섹션을 추가했다.

위치BeforeAfter
메인 퀴즈 페이지~200자~500자
개별 퀴즈 BeforeStage~80자~400자 (설명 3섹션 + 기존 리스트)

각 퀴즈에 “퀴즈 소개”, “어떤 문제가 나오나요?”, “난이도 안내” 3개 섹션을 추가하여 페이지별 고유한 콘텐츠를 확보했다.

내부 링크 확장

thin content 해소와 함께 크롤링 경로도 개선했다. BeforeStage와 Result 화면에 두 종류의 내부 링크를 추가했다.

퀴즈관련 페이지 링크교차 퀴즈 링크
실루엣 퀴즈/list (포켓몬 도감)특성, 타입, 타입 상성 퀴즈
특성 퀴즈/ability (특성 도감)실루엣, 타입, 타입 상성 퀴즈
타입 퀴즈/list (포켓몬 도감)실루엣, 특성, 타입 상성 퀴즈
타입 상성 퀴즈/type-effectiveness (계산기)실루엣, 특성, 타입 퀴즈

관련 도감 페이지로의 링크와 다른 퀴즈로의 교차 링크를 추가하여 크롤러가 사이트 내 페이지를 더 효율적으로 탐색할 수 있게 했다.

변경 10~12: 코드 패턴 통일과 HowTo 추가

컴포넌트 패턴 통일

코드 리뷰를 거쳐 4개 퀴즈의 BeforeStage와 Result 컴포넌트를 동일한 패턴으로 통일했다. 인라인 렌더링을 공유 컴포넌트(OtherQuizLink)로 추출하고, 링크 색상을 프로젝트 디자인 시스템(text-primary-2)에 맞췄다.

HowTo JSON-LD 추가

개별 퀴즈 4개 페이지에 HowTo 타입 JSON-LD를 추가했다. 3단계 구조로 퀴즈 진행 과정을 설명한다.

Step내용
1시작하기 버튼으로 20문제 4지선다 시작
2(퀴즈별 상이) 문제 풀이 방법 안내
3점수, 정답률, 소요 시간 및 오답 확인

참고: Google은 2023년 9월부터 HowTo 리치 결과 지원을 축소했다. 리치 결과로 직접 표시되지는 않지만, 페이지 내용을 이해하는 보조 신호로는 여전히 활용될 수 있다.

WebPage description 보강

개별 퀴즈 4개의 WebPage JSON-LD description을 quiz.constants.ts의 상세 설명 텍스트와 동기화했다.

// Before
description: '검은 실루엣만 보고 포켓몬 이름을 맞춰보세요!'

// After
description: '실루엣 퀴즈는 검은 그림자로 가려진 포켓몬의 외형만 보고 어떤 포켓몬인지 맞추는 퀴즈입니다.'

결과

전체 변경 요약

항목BeforeAfter
BreadcrumbList오류 (잘못된 name)정상
ItemList미적용4개 퀴즈 목록 구조화
primaryImageOfPage1개 (문자열)5개 (ImageObject)
url 필드1개5개
메타데이터 관리인라인 (~150줄 중복)상수 1개 파일
Title 구체성일반적 제목퀴즈 종류·문제 수 명시
앵커 텍스트”시작하기 →” (동일){퀴즈명} 시작하기 →“
메인 퀴즈 텍스트~200자~500자
개별 퀴즈 텍스트~80자~400자
내부 링크 (BeforeStage)0개관련 1개 + 교차 3개
수정 파일 수약 30개

검증 방법

Google Rich Results Test에서 각 퀴즈 페이지 URL을 입력하여 구조화 데이터의 유효성을 확인했다. BreadcrumbList, ItemList, WebPage 모두 오류 없이 감지되었다.

정리하며

이번 작업에서 가장 효과적이었던 것은 외부 SEO 피드백을 체계적으로 반영한 것이다. Title/Description 재작성, 앵커 텍스트 구체화, thin content 해소는 내부에서는 놓치기 쉬운 부분이었다.

JSON-LD 구조화 데이터는 리치 결과 노출이라는 직접적 효과도 있지만, 검색엔진이 페이지의 맥락을 정확히 이해하도록 돕는 간접적 효과가 더 크다. BreadcrumbList 하나의 name 오류가 전체 탐색 경로 표시를 망칠 수 있듯, 작은 실수가 큰 영향을 미칠 수 있다.

다음 편에서는 기술 도감 페이지의 URL 마이그레이션과 useSearchParams에서 Context로의 전환 과정을 다룬다.


포케코리아 SEO 개선기 시리즈

참고 자료