Next.js × microCMS で JsonLd(構造化データ)を実装して Google Search Console のリッチリザルトに対応した話

Next.js × microCMS で JsonLd(構造化データ)を実装して Google Search Console のリッチリザルトに対応した話

はじめに

ポートフォリオサイトやブログを作っていると、Google Search Console にこんなメッセージが表示されることがあります。

Google は、構造化データを使用してページのコンテンツを認識し、そのコンテンツを「リッチリザルト」と呼ばれる情報が豊富な検索結果に表示します。

最初は「なんかSEOっぽいものかな?」くらいの認識でしたが、調べていくと構造化データ(Structured Data / JSON-LD)は、検索エンジンに対して「このページは何のページなのか」を正確に伝えるための重要な仕組みでした。

今回は、Next.js(App Router) + microCMS 構成の個人サイトに実装したので、そのうちの一部をまとめます。

  • BlogPosting
  • Google Rich Results Test 対応

JSON-LDとは?

JSON-LD は Google に対して「このページの意味」を伝えるためのデータ形式です。

例えば普通のHTMLだけだと、Googleから見ると以下のようになります。

<h1>Next.jsの記事タイトル</h1>

<p>2026-05-01</p>

これだけでは:

  • これはブログ記事なのか
  • 著者は誰なのか
  • 公開日は何なのか
  • サイト運営者は誰なのか

が曖昧です。

そこで JSON-LD を使うと、Google に明確に伝えられます。

{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Next.jsの記事タイトル",
"author": {
"@type": "Person",
"name": "Tsukasa"
 }
}

これによって Google は

  • これはブログ記事
  • 著者は Tsukasa
  • 記事タイトルはこれ

を理解できるようになります。


実装方針

今回の構成はこんな感じです。

src/

├ lib/

│ ├ metadata.ts

│ └ schema.ts

├ components/

│ └ common/

│ └ JsonLd.tsx

└ app/

├ about/page.tsx

├ blog/page.tsx

└ blog/[slug]/page.tsx

責務を分離しています。

JsonLd コンポーネント

まずは共通コンポーネントを作成。

type Props = {
 data: object;
};

export function JsonLd({ data }: Props) {
 return (
 <script
 type="application/ld+json"
 dangerouslySetInnerHTML={{
 __html: JSON.stringify(data),
 }}
 />
 );
}

JSON-LD は最終的に:

<script type="application/ld+json">

として出力されます。

毎回 page.tsx に直書きすると冗長なので、コンポーネント化しておくと便利です。


schema.ts にまとめる

次に、構造化データ生成を schema.ts に集約します。

const BASE_URL = process.env.BASE_URL || 'https://ambientworks.app';
export const personSchema = {
 '@type': 'Person',
 name: 'Tsukasa',
 url: ${BASE_URL}/about,
 image: ${BASE_URL}/images/profile.png,
 sameAs: [
  'https://github.com/ambientworks',
  'https://x.com/tuka3_amb86',
 ],
};

こうしておくと:

  • BlogPosting
  • WebSite
  • Aboutページ

すべてで同じ人物情報を使い回せます。

Googleから見ても「同じ人物」として認識しやすくなります。


BlogPosting の実装

ブログ記事ページでは BlogPosting を使用しました。

export function createBlogPostingSchema({
 title,
 description,
 publishedAt,
 updatedAt,
 image,
 path,
}:{
 title: string;
 description: string;
 publishedAt: string;
 updatedAt?: string;
 image?: string;
 path: string;
}) {
 return {
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: title,
  description,
  image: image || ${BASE_URL}/images/ogp.png,
  datePublished: publishedAt,
  dateModified: updatedAt || publishedAt,

  mainEntityOfPage: {
   '@type': 'WebPage',
   '@id': ${BASE_URL}${path},
  },

  author: personSchema,
  publisher: {
   '@type': 'Organization',
   name: 'ambientworks',
   logo: {
    '@type': 'ImageObject',
    url: ${BASE_URL}/images/ogp.png,
   },
  },
 };
}

BlogPosting をページへ組み込む

const jsonLd = createBlogPostingSchema({
 title: blog.title,
 description: blog.description,
 publishedAt: blog.publishedAt,
 updatedAt: blog.updatedAt,
 image: blog.eyecatch?.url,
 path: /blog/${blog.id},
});

return (
 <>
  <JsonLd data={jsonLd} />
  <main>
   ...
  </main>
 </>
);

これだけで Google が BlogPosting を認識してくれます。


Rich Results Test

実装後は Google のテストツールで確認できます。

https://search.google.com/test/rich-results

最終的に:

1 件の有効なアイテムを検出しました

と表示されればOK。


最近、Google Search Consoleが更新されないかなと確認するのが楽しみになっている自分がいます。

一つずつ、理解していこう。