기술 블로그를 운영하다 보면 메타태그, canonical, sitemap까지는 신경 쓰는데 구조화 데이터는 뒤로 미루기 쉽습니다. 눈에 바로 보이는 UI가 아니고, 넣었다고 해서 화면이 달라지는 것도 아니기 때문입니다. 그런데 Google Search Central 문서를 차분히 읽어보면, 구조화 데이터는 “장식용 마크업”이 아니라 검색 엔진이 페이지를 더 정확하게 이해하도록 돕는 설명 레이어에 가깝습니다. 특히 블로그 글 페이지에서는 ArticleBreadcrumbList가 서로 다른 역할을 하면서 꽤 좋은 조합이 됩니다.

Google의 Article 구조화 데이터 문서는 뉴스, 블로그, 스포츠 기사 페이지에 Article 마크업을 추가하면 Google이 그 페이지를 더 잘 이해하고, 검색 결과에서 더 나은 title text, 이미지, 날짜 정보를 보여주는 데 도움이 될 수 있다고 설명합니다. Breadcrumb 문서는 breadcrumb trail이 페이지의 사이트 내 위치를 보여주며, 사용자가 계층을 이해하는 데 도움이 된다고 설명합니다. 즉 Article은 “이 페이지가 어떤 문서인가”를 설명하고, BreadcrumbList는 “이 문서가 사이트 구조 안에서 어디에 있나”를 설명합니다.

둘은 겹치는 정보가 아니라 서로 다른 층의 정보입니다. 그래서 저는 블로그 글 상세 페이지에서는 이 둘을 함께 넣는 편을 추천합니다.

검색/신뢰구조화 데이터

Article과 Breadcrumb 구조화 데이터를 넣어야 하는 이유와 구현법

블로그 글 페이지에 Article과 Breadcrumb 구조화 데이터를 왜 넣는지, 그리고 Next.js App Router에서 JSON-LD로 어떻게 구현하면 되는지 정리합니다. Google 공식 문서 기준으로 어떤 필드를 넣어야 하는지와 흔한 실수도 함께 설명합니다.

핵심 1

먼저 결론: 블로그 글 페이지라면 이 두 가지를 같이 넣는 편이 좋다

핵심 2

Article은 뉴스 사이트만 쓰는 것이 아니다

핵심 3

BreadcrumbList는 시각적 빵 부스러기 UI를 꼭 복제해야 한다는 뜻이 아니다

구조화 데이터검색/신뢰article breadcrumb schema
문서 자체 설명은 Article, 사이트 계층 설명은 BreadcrumbList가 맡는 구조화 데이터 흐름

먼저 결론: 블로그 글 페이지라면 이 두 가지를 같이 넣는 편이 좋다

제 기준에서는 글 상세 페이지에 아래 두 개를 함께 두는 편이 가장 자연스럽습니다.

  • BlogPosting 또는 Article
  • BreadcrumbList

왜냐하면 각각이 해결하는 문제가 다르기 때문입니다.

  • Article: 제목, 발행일, 수정일, 작성자, 대표 이미지처럼 문서 자체를 설명
  • BreadcrumbList: 홈, 아카이브, 현재 글처럼 사이트 안에서의 위치를 설명

특히 작은 기술 블로그에서는 페이지 수가 많지 않더라도, 시간이 지나면서 글이 늘어나면 “이 페이지는 독립 글”이라는 정보와 “이 글은 어떤 흐름 안에 있나”라는 정보가 같이 중요해집니다. 구조화 데이터는 바로 그 차이를 검색 엔진에 명시적으로 알려주는 방법입니다.

1. Article은 뉴스 사이트만 쓰는 것이 아니다

이 부분을 많이 오해합니다. Google Article 문서에는 Article, NewsArticle, BlogPosting이 모두 기반 타입으로 언급됩니다. 즉 기술 블로그 글은 NewsArticle이 아니라 BlogPosting이나 일반 Article로 표현하면 됩니다.

문서에서 중요한 포인트는 이겁니다.

  • Google이 페이지를 더 잘 이해하도록 돕는다
  • 더 나은 제목, 이미지, 날짜 정보에 도움이 될 수 있다
  • 하지만 구조화 데이터를 넣는다고 rich result가 보장되지는 않는다

Google도 명시적으로, 구조화 데이터를 소비하는 기능이 검색 결과에 반드시 나타난다고 보장하지 않는다고 설명합니다. 이 문장은 중요합니다. 구조화 데이터는 “마법 버튼”이 아니라 “이해를 돕는 신호”입니다. 그래서 없는 것보다 있는 편이 좋지만, 구조화 데이터만 넣고 본문 품질이나 메타데이터가 부실하면 기대한 결과가 나오지 않을 수 있습니다.

2. BreadcrumbList는 시각적 빵 부스러기 UI를 꼭 복제해야 한다는 뜻이 아니다

Google의 breadcrumb 문서는 breadcrumb trail이 페이지의 사이트 계층상 위치를 나타낸다고 설명합니다. 중요한 건 “사이트 계층”입니다. 즉 breadcrumb는 단순히 화면에 보이는 링크 모음이 아니라, 이 페이지가 어느 상위 구조 아래에 놓이는지를 말해 줍니다.

블로그 기준으로는 보통 이런 흐름이면 충분합니다.

홈 > 블로그 > 현재 글

혹은 카테고리 구조를 실제로 안정적으로 운영한다면:

홈 > 검색/신뢰 > 현재 글

여기서 핵심은 실제 탐색 구조와 구조화 데이터가 크게 어긋나지 않아야 한다는 점입니다. 없는 카테고리를 breadcrumb에 억지로 넣거나, URL에는 없는 경로를 구조화 데이터에서만 만들어내는 건 피하는 편이 좋습니다.

3. 왜 굳이 둘을 같이 넣어야 할까

둘 중 하나만 넣어도 의미는 있습니다. 하지만 같이 넣으면 검색 엔진 입장에서 페이지를 읽는 맥락이 더 또렷해집니다.

예를 들어 같은 글 페이지를 생각해보면:

  • Article만 있으면: “이건 블로그 글이구나”
  • BreadcrumbList만 있으면: “이 페이지는 블로그 계층 안에 있구나”
  • 둘 다 있으면: “이건 블로그 안의 특정 글이며, 제목·날짜·작성자·이미지가 이렇구나”

즉 하나는 문서 모델이고, 하나는 정보 구조 모델입니다. 이 조합이 검색 결과와 내부 구조 양쪽 설명에 다 도움이 됩니다.

이전 글에서 정리한 slug, 날짜, 카테고리 구조 글과 연결해서 보면, 구조화 데이터는 이미 정리된 URL/계층 원칙을 검색 엔진용으로 다시 서술하는 층이라고 볼 수 있습니다.

4. Google 기준으로 Article에 어떤 필드를 넣는가

Google의 Article 문서를 보면 “필수(required) 속성은 없고, 해당 페이지에 적용되는 recommended properties를 최대한 많이 넣으라”고 안내합니다. 이 점은 생각보다 중요합니다. 즉 “몇 개를 넣어야 자격이 된다”보다, 내 문서에 실제로 존재하는 정보를 정직하게 설명하는 편이 중요합니다.

블로그 글 기준으로 제가 기본값으로 넣는 필드는 이렇습니다.

  • @context
  • @type
  • headline
  • image
  • datePublished
  • dateModified
  • author
  • mainEntityOfPage

특히 Google 문서는 datePublished는 ISO 8601 형식으로 쓰고, 가능하면 타임존 정보까지 넣으라고 설명합니다. headline은 간결한 제목을 권장하고, imageauthor도 가능하면 함께 제공하라고 안내합니다.

예시는 이런 식입니다.

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Article과 Breadcrumb 구조화 데이터를 넣어야 하는 이유와 구현법",
  "image": [
    "https://www.example.com/articles/article-breadcrumb-schema/schema-flow.png"
  ],
  "datePublished": "2026-04-08T09:00:00+09:00",
  "dateModified": "2026-04-08T09:00:00+09:00",
  "author": {
    "@type": "Person",
    "name": "Taejoo",
    "url": "https://www.example.com/about"
  },
  "mainEntityOfPage": "https://www.example.com/blog/article-breadcrumb-schema"
}

여기서 중요한 건 거짓 정보를 채우지 않는 것입니다. 작성자 페이지가 아직 없다면 없는 URL을 만들기보다, 실제로 존재하는 소개 페이지를 연결하는 편이 낫습니다.

5. BreadcrumbList는 최소 두 단계 이상이어야 하고, 순서가 중요하다

Google의 breadcrumb 문서는 breadcrumb를 정의할 때 최소 두 개 이상의 ListItem이 들어간 BreadcrumbList가 필요하다고 설명합니다. 각 ListItem에는 item, name, position이 핵심이고, 마지막 항목은 item이 없어도 Google이 현재 페이지 URL을 사용할 수 있다고 안내합니다.

블로그 글 상세 페이지라면 보통 이 정도가 가장 단순합니다.

{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "홈",
      "item": "https://www.example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "블로그",
      "item": "https://www.example.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Article과 Breadcrumb 구조화 데이터를 넣어야 하는 이유와 구현법"
    }
  ]
}

Google 문서는 또 여러 경로로 접근 가능한 페이지라면 multiple breadcrumb trails도 지원한다고 설명합니다. 다만 1인 개발 블로그에서는 breadcrumb를 지나치게 복잡하게 만들기보다, 가장 대표적인 탐색 경로 하나만 먼저 명확히 두는 편이 관리가 쉽습니다.

6. Next.js App Router에서는 JSON-LD를 <script>로 넣는 것이 현재 권장 방식이다

Next.js 공식 JSON-LD 가이드는 현재 권장 방식으로 layout.jspage.js 컴포넌트 안에 <script type="application/ld+json"> 태그를 렌더링하라고 설명합니다. 그리고 JSON.stringify는 XSS를 막지 못하므로, 예시처럼 < 문자를 \u003c로 치환하는 등 조직 기준에 맞는 sanitization이 필요하다고 명시합니다.

즉 Next.js에서는 metadata API에 모든 구조화 데이터를 밀어 넣는 방식보다, JSON-LD용 <script>를 명시적으로 렌더링하는 편이 더 자연스럽습니다.

예를 들면 블로그 글 페이지에서 이렇게 넣을 수 있습니다.

import { getSiteUrl } from "@/lib/site-url";

export default async function BlogPostPage({ params }: BlogPostPageProps) {
  const { slug } = await params;
  const article = await getPublishedArticleBySlug(slug);

  if (!article) return null;

  const siteUrl = getSiteUrl();
  const articleUrl = `${siteUrl}/blog/${article.slug}`;

  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: article.title,
    image: article.coverImage ? [`${siteUrl}${article.coverImage}`] : undefined,
    datePublished: `${article.publishedAt}T09:00:00+09:00`,
    dateModified: `${article.updatedAt ?? article.publishedAt}T09:00:00+09:00`,
    author: {
      "@type": "Person",
      name: "Taejoo",
      url: `${siteUrl}/about`,
    },
    mainEntityOfPage: articleUrl,
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
        }}
      />
      {/* page body */}
    </>
  );
}

이 패턴의 장점은 분명합니다.

  • 실제 페이지 데이터와 구조화 데이터를 같은 함수 안에서 만들기 쉽다
  • 절대 URL 계산을 getSiteUrl() 기준으로 통일할 수 있다
  • 발행일, 수정일, 대표 이미지와 쉽게 연결된다

7. Breadcrumb도 같은 페이지에서 함께 렌더링할 수 있다

구현 방식은 Article과 거의 같습니다. 저는 보통 두 개를 따로 만들고, 한 페이지 안에서 <script> 두 개로 렌더링하거나 배열로 묶어서 한 번에 내보냅니다.

예를 들면:

const breadcrumbJsonLd = {
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  itemListElement: [
    {
      "@type": "ListItem",
      position: 1,
      name: "홈",
      item: `${siteUrl}/`,
    },
    {
      "@type": "ListItem",
      position: 2,
      name: "블로그",
      item: `${siteUrl}/blog`,
    },
    {
      "@type": "ListItem",
      position: 3,
      name: article.title,
    },
  ],
};

그리고 이렇게 함께 넣을 수 있습니다.

const structuredData = [articleJsonLd, breadcrumbJsonLd];

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(structuredData).replace(/</g, "\\u003c"),
  }}
/>

이 방식은 유지보수에 꽤 좋습니다. 글 제목이 바뀌거나 slug가 바뀔 때도 구조화 데이터가 같은 데이터 원천을 따라가므로 덜 어긋납니다.

8. 구조화 데이터를 넣을 때 가장 자주 생기는 실수

제가 실제로 많이 보는 실수는 이 정도입니다.

1. 화면과 다른 정보를 넣는다

구조화 데이터의 제목, 날짜, 작성자 정보가 실제 페이지와 다르면 신뢰가 떨어집니다. Google도 일반 가이드에서 페이지 내용과 일치하는 구조화 데이터를 권장합니다.

2. 상대 경로를 그대로 넣는다

특히 image, mainEntityOfPage, breadcrumb item은 절대 URL로 관리하는 편이 훨씬 안전합니다.

3. breadcrumb 경로가 실제 탐색 구조와 다르다

실제 사이트에는 없는 카테고리를 breadcrumb에 넣거나, URL 구조와 동떨어진 계층을 억지로 구성하면 관리가 꼬입니다.

4. 마지막 breadcrumb에도 잘못된 URL을 넣는다

Google 문서상 마지막 항목은 item이 없어도 됩니다. 현재 페이지 URL과 다른 주소를 억지로 넣는 것보다 생략이 더 낫습니다.

5. 구조화 데이터를 넣고 검증을 안 한다

Google은 Rich Results Test로 검증하고, 배포 후 URL Inspection과 Search Console의 relevant rich result report를 보라고 안내합니다. 즉 넣는 것만큼 검증도 중요합니다.

6. JSON 문자열을 그대로 삽입한다

Next.js 공식 가이드가 특별히 언급하는 부분입니다. JSON.stringify 결과를 그대로 넣는 건 XSS 관점에서 안전하지 않을 수 있으므로, 최소한 < 문자를 치환하는 등 기본 sanitization이 필요합니다.

9. 구현 후 검증 순서는 이렇게 가면 된다

Google의 article 문서와 breadcrumb 문서 모두 거의 같은 순서를 안내합니다.

  1. 구조화 데이터 추가
  2. Rich Results Test로 검증
  3. 몇 개 페이지 배포
  4. URL Inspection으로 Google이 페이지를 어떻게 보는지 확인
  5. 필요하면 recrawl 요청
  6. Search Console의 rich result status report를 모니터링

이 순서가 좋은 이유는 분명합니다. 로컬에서만 확인하고 끝내지 않고, 실제로 Google이 접근 가능한 HTML에서 구조화 데이터가 보이는지까지 확인할 수 있기 때문입니다.

간단한 로컬 확인은 shell로도 가능합니다.

curl.exe https://www.example.com/blog/article-breadcrumb-schema | Select-String 'application/ld+json'

물론 이걸로 스키마 유효성까지 보장되진 않지만, 최소한 HTML에 JSON-LD가 실려 있는지는 빠르게 확인할 수 있습니다.

10. 이 레포에 적용한다면 어떤 모양이 가장 자연스러울까

이 블로그 구조를 기준으로 보면, 글 상세 페이지는 이미 제목, 요약, 발행일, 수정일, 대표 이미지, slug를 가지고 있습니다. 그래서 구조화 데이터로 옮기기 좋은 조건이 꽤 갖춰져 있습니다.

저라면 이 레포에는 이렇게 적용합니다.

  • 글 상세 페이지에 BlogPosting JSON-LD 추가
  • 같은 페이지에 BreadcrumbList 추가
  • 작성자는 우선 Person 1명으로 시작
  • author URL은 /about
  • breadcrumb는 홈 > 블로그 > 현재 글
  • coverImage가 있는 글만 image 포함

이렇게 시작하면 구조화 데이터를 위해 별도 CMS 필드를 왕창 늘리지 않아도 됩니다. 중요한 건 시작을 단순하게 하되, 실제 데이터와 일치시키는 것입니다.

마무리

ArticleBreadcrumbList 구조화 데이터는 서로 다른 역할을 합니다. 하나는 문서 자체를 설명하고, 다른 하나는 그 문서의 위치를 설명합니다. 그래서 블로그 글 상세 페이지에서는 둘을 함께 넣는 편이 가장 자연스럽습니다.

Google 공식 문서를 기준으로 보면, Article은 뉴스 사이트만을 위한 것이 아니라 BlogPosting까지 포함하고, BreadcrumbList는 최소 두 단계 이상의 실제 계층을 반영해야 합니다. Next.js에서는 현재 권장 방식대로 JSON-LD <script>를 렌더링하고, 문자열을 안전하게 삽입하는 처리를 함께 하는 편이 좋습니다.

결국 핵심은 화려한 스키마를 많이 넣는 것이 아니라, 페이지에 실제로 존재하는 제목, 날짜, 작성자, 이미지, 계층 정보를 검색 엔진이 이해하기 쉬운 형태로 다시 설명하는 것입니다. 메타데이터와 canonical 정리가 아직 덜 됐다면 meta title/description 글www/non-www/canonical 글을 함께 보고 오는 흐름도 잘 맞습니다.

참고 문서