Tenten AIGEO
블로그로 돌아가기
기술 AEO 구현도입·실행

Next.js App Router에 JSON-LD 삽입하기: Server Component로 구조화 데이터 구현

Next.js App Router의 Server Component에서 `<script type="application/ld+json">`을 직접 출력하면 구조화 데이터가 초기 HTML에 포함되어 AI 크롤러가 JavaScript를 실행하지 않고도 읽을 수 있습니다. 최소 구성의 page.tsx 예제부터 @graph를 활용한 다중 엔터티 연결, 배포 전 curl 검증 절차까지 설명합니다.

Tenten GEO 팀게시일 2026-06-115 분 소요
서버에서 출력한 구조화 데이터가 AI 크롤러가 읽는 초기 HTML로 직접 전달되는 과정을 나타낸 도식

App Router에서 JSON-LD를 삽입하는 가장 안정적인 방법은 Server Component에서 `<script type="application/ld+json">`을 직접 출력하는 것입니다. 이렇게 하면 구조화 데이터가 서버가 반환하는 초기 HTML에 포함되므로 AI 크롤러가 JavaScript를 실행하지 않고도 읽을 수 있습니다. 반면 useEffect나 Google Tag Manager로 나중에 삽입한 JSON-LD는 답변 엔진에 사실상 존재하지 않는 것과 같습니다. 대부분의 크롤러는 페이지의 hydration이 끝날 때까지 기다리지 않고 수집을 마치기 때문입니다.

반드시 서버에서 출력해야 하는 이유

핵심은 출력 시점입니다. Server Component는 요청을 처리하는 순간 JSON-LD를 HTML 문자열에 기록합니다. 따라서 GPTBot, ClaudeBot, PerplexityBot, Google-Extended 같은 크롤러가 처음 가져가는 파일에 완전한 스키마가 들어 있습니다. 클라이언트 측 삽입 방식은 브라우저가 번들을 내려받아 실행할 때까지 기다려야 하지만, 대부분의 AI 크롤러는 이 단계 자체를 수행하지 않습니다. 특히 오판하기 쉬운 지점이 있습니다. Google Rich Results Test는 JavaScript를 렌더링하므로 클라이언트에서 삽입한 데이터도 정상으로 표시됩니다. 테스트를 통과했다고 생각하기 쉽지만, 실제 답변 엔진이 읽는 것은 렌더링 전 원본 HTML이며 여기에는 스키마가 없습니다. 고객사의 기술 감사를 진행할 때 가장 자주 발견되면서도 놓치기 쉬운 감점 요인입니다.

최소 구현 예제: page.tsx 한 파일로 완성하기

구현 코드는 매우 짧습니다. 별도 패키지나 API route 없이 글을 렌더링하는 Server Component 안에서 처리할 수 있습니다. 핵심은 데이터를 가져오고, 객체를 구성하고, 스크립트를 출력하는 3가지입니다.

  1. 기본적으로 Server Component인 `app/blog/[slug]/page.tsx`에서 먼저 `await`로 현재 글의 데이터를 가져옵니다.
  2. 순수 JavaScript 객체 `jsonLd`를 구성합니다. 앞부분에 `"@context": "https://schema.org"`와 `"@type": "Article"`을 넣고, 나머지 필드(headline, datePublished, author, image)는 글 데이터로 동적으로 채웁니다.
  3. 반환하는 JSX 앞부분에 `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />`를 배치합니다.
  4. 클라이언트 라이브러리를 import하거나 파일 상단에 "use client"를 추가하지 마십시오. 이를 추가하는 순간 Client Component로 바뀌어 앞선 작업의 효과가 사라집니다.

@graph로 여러 엔터티 연결하기

한 페이지에는 보통 하나 이상의 스키마가 필요합니다. 브랜드 자체는 Organization, 글은 Article, 탐색 경로는 BreadcrumbList, 문답 영역은 FAQPage에 해당합니다. 4개의 독립적인 script 태그를 출력하기보다 `@graph` 배열을 사용해 하나의 JSON-LD 객체에 담고 `@id`로 서로 연결하는 편이 깔끔합니다. 예를 들어 Article의 `publisher`가 Organization의 `@id`를 가리키게 하고, BreadcrumbList의 마지막 항목은 현재 페이지의 `@id`를 가리키게 할 수 있습니다. 그러면 답변 엔진이 한 번의 분석으로 전체 엔터티 관계를 파악할 수 있고, 서로 충돌하거나 중복된 노드로 인식할 가능성도 줄어듭니다.

  • Organization과 WebSite: `app/layout.tsx`에 배치해 사이트 전체에서 한 번만 공유합니다. 페이지마다 반복해서 선언하지 마십시오.
  • Article 또는 BlogPosting: 글의 `page.tsx`에 배치하고 각 필드를 현재 페이지의 데이터로 동적으로 채웁니다.
  • BreadcrumbList: 라우팅 계층에 맞춰 구성합니다. AI가 사이트 구조를 이해하는 데 특히 유용합니다.
  • FAQPage: 화면에 실제 문답 영역이 있을 때만 추가하며, `acceptedAnswer`의 문구도 화면에 표시되는 내용과 일치해야 합니다. 스키마를 채우기 위해 존재하지 않는 FAQ를 만들지 마십시오.
Server Component가 초기 HTML에 JSON-LD를 출력하는 반면, 클라이언트 측에서 삽입한 데이터는 AI 크롤러가 읽지 못하는 과정을 보여 주는 인포그래픽
서버에서 출력한 JSON-LD는 초기 HTML에 포함되지만, 클라이언트에서 삽입한 데이터는 크롤러가 기다려 주지 않습니다.

유지보수 가능한 JSON-LD 만들기

스키마 객체를 여러 페이지에 흩어 놓으면 3개월 뒤에는 누구도 선뜻 수정하지 못합니다. 저희는 `lib/schema.ts`를 별도로 만들고 `schema-dts`의 타입(`import type { Article, WithContext } from "schema-dts"`)으로 각 생성 함수의 반환값을 제한합니다. 필드명이나 타입이 잘못되면 컴파일 단계에서 TypeScript 오류가 발생합니다. 페이지에서는 `buildArticleSchema(post)`만 호출해 타입 안전성이 보장된 객체를 받은 뒤 출력합니다. 이후 생성 함수 하나만 수정해도 사이트 전체에 반영됩니다. URL은 항상 절대 경로를 사용하고 `SITE_URL` 상수에서 일괄 관리해 공식 사이트와 프리뷰 도메인 사이에서 `@id`가 어긋나지 않도록 합니다.

자주 발생하는 오류와 배포 전 검증

  • JSON-LD의 `url`, `image`, `datePublished`가 화면의 실제 콘텐츠와 일치하지 않으면 답변 엔진이 차이를 감지해 페이지 전체에 대한 신뢰도를 낮춥니다.
  • `@id`나 `url`에 상대 경로를 사용하면 프리뷰 도메인으로 전환했을 때 잘못된 위치를 가리킬 수 있습니다.
  • `<` 이스케이프를 빠뜨리거나 페이지 상단에 실수로 `"use client"`를 추가하면 스크립트가 클라이언트에서 출력됩니다.
  • 배포 전 Google Rich Results Test로 문법을 확인한 다음, `curl`로 페이지의 원본 HTML을 직접 가져와 스크립트가 브라우저 DevTools에만 나타나는 것이 아니라 초기 응답에 실제로 포함됐는지 확인합니다. AI 크롤러가 보는 내용을 그대로 재현하는 절차이므로 이 단계가 가장 중요합니다.
지금까지 감사한 웹사이트를 보면 JSON-LD 문제의 대부분은 스키마 작성 오류가 아니라 데이터가 클라이언트 측에만 존재한다는 데서 발생했습니다. 서버가 반환한 HTML에 없다면 AI는 해당 스키마가 없는 것으로 판단합니다.Tenten GEO Technical Audit

구현에서 인용까지

JSON-LD를 Server Component로 옮기는 것은 콘텐츠를 ‘기계가 읽을 수 있는’ 상태로 만드는 최소 요건입니다. 다음 단계에서는 엔터티 명칭, 내부 링크, 페이지 간 일관성을 정렬해 AI가 신뢰할 수 있는 출처로 안정적으로 인용하도록 해야 합니다. 사이트의 구조화 데이터가 크롤러가 보는 HTML에 실제로 포함됐는지 확신하기 어렵다면 30분 GEO 진단을 예약해 보십시오. 렌더링된 화면만 확인하는 대신 실제로 수집한 소스 코드를 바탕으로 누락된 부분을 점검해 드립니다.

자주 묻는 질문

Next.js App Router에 JSON-LD를 추가하려면 어떻게 해야 하나요?
Server Component(page.tsx 또는 layout.tsx)에서 스키마 객체를 구성한 뒤, `dangerouslySetInnerHTML`을 적용한 `<script type="application/ld+json">`으로 출력하고 문자열의 `<`를 `<`로 이스케이프합니다. 그러면 구조화 데이터가 초기 HTML에 포함되어 AI 크롤러가 JavaScript를 실행하지 않고도 읽을 수 있습니다.
next/head나 Metadata API로 JSON-LD를 넣어도 되나요?
App Router에서는 더 이상 next/head를 사용하지 않으며 Metadata API도 임의의 script 태그 출력을 지원하지 않습니다. Next.js는 Server Component의 JSX에서 `<script type="application/ld+json">`을 직접 렌더링하는 방식을 공식적으로 권장합니다. 현재 JSON-LD를 삽입하는 가장 안정적인 방법이자 크롤러가 읽기에도 가장 용이한 방식입니다.
GTM이나 useEffect로 구조화 데이터를 삽입하는 방식을 권장하지 않는 이유는 무엇인가요?
두 방식 모두 클라이언트 측에서 실행되므로 JSON-LD는 브라우저의 hydration 이후에 나타납니다. 대부분의 AI 크롤러는 초기 HTML만 가져오고 JavaScript를 실행하지 않기 때문에 스키마를 볼 수 없습니다. 서버 출력 방식으로 바꿔야 구조화 데이터가 답변 엔진에 실제로 읽히고 참조될 수 있습니다.

다음 단계

AI 답변에서 우리 브랜드는 얼마나 보일까요?

30분 GEO 진단을 통해 주요 AI 엔진에서의 가시성 격차와 우선 개선 과제를 확인해 보세요.

30분 진단 예약