Next.js App Router × JSON-LD 完全実装ガイド:Cursor・Claude Code・Copilotでの自動生成まで
各章へクイックジャンプ:
Next.js App Routerで開発しているなら、JSON-LDの実装は思ったより簡単で、 かつ動的コンテンツとの相性が抜群です。 さらに Cursor・Claude Code・GitHub Copilot などのAI開発ツール を活用すれば、 適切なプロンプトを与えるだけでスキーマコードを自動生成できます。 本章では、Next.jsでのJSON-LD実装パターンから、 AIコーディングツールに最高の出力を出させるプロンプト設計まで、 実際のコードと共に解説します。
- Next.js App Router での JSON-LD の基本配置(layout.tsx vs page.tsx)
- 動的ルート(
[slug])での CMS 連携 JSON-LD 生成パターン - TypeScript による JSON-LD の型安全な定義
metadataAPI と JSON-LD の役割分担- Cursor・Claude Code・GitHub Copilot に JSON-LD を自動生成させるプロンプト
- 開発環境での確認方法とデバッグ手順
1. App Router における JSON-LD の配置戦略
Next.js の Pages Router では next/head を使っていましたが、 App Router では <script> タグを JSX 内に直接記述します。 これは Server Component で動作し、クライアントサイドの JavaScript を必要としません。どのファイルに置くかは、スキーマの「スコープ」で決まります。
サイト全体に共通のスキーマ。WebSite・Organization はルートの app/layout.tsx に置くのが最適。 全ページで同じ組織情報が出力されます。
ページ固有のスキーマ。Article・BreadcrumbList・Product・FAQPage はそれぞれの page.tsx に置く。 動的ルートでは params や fetch データを使って動的に生成。
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: { default: 'サイト名', template: '%s | サイト名' },
description: 'サイトの説明',
};
// JSON-LD は metadata とは別に定義
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': 'https://example.com/#website',
'url': 'https://example.com',
'name': 'サイト名',
'inLanguage': 'ja',
'publisher': { '@id': 'https://example.com/#organization' },
},
{
'@type': 'Organization',
'@id': 'https://example.com/#organization',
'name': '株式会社サンプル',
'url': 'https://example.com',
'logo': {
'@type': 'ImageObject',
'url': 'https://example.com/logo.png',
},
'sameAs': ['https://twitter.com/example'],
},
],
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\u003c')
}}
/>
</head>
<body>{children}</body>
</html>
);
}@graph を使って複数スキーマを1つにまとめる@graph は JSON-LD の仕様で、複数のスキーマオブジェクトを1つの <script> タグにまとめる方法です。[...](配列)でも同じことができますが、@graph を使うと スキーマ間の @id による参照がより明確になり、Googleのナレッジグラフとの連携品質が向上します。JSON.stringify(jsonLd) だけでは、悪意ある文字列中の < を完全には無害化できません。 CMS由来のタイトルや説明文を埋め込む場合は、JSON.stringify(jsonLd).replace(/</g, '\\u003c') ではなく、 実際のコードでは JSON.stringify(jsonLd).replace(/</g, '\u003c') のように 追加の置換を行う実装が安全です。2. 動的ルートでの JSON-LD 生成:CMS連携パターン
ブログや製品ページのように、コンテンツがCMSやデータベースから取得される場合、 JSON-LDも動的に生成する必要があります。Next.js の generateMetadata に似た発想で、 データフェッチと JSON-LD 生成を page.tsx 内で完結させます。
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
// CMSや独自データ取得関数(実装はプロジェクトに合わせる)
async function getPost(slug: string) {
const res = await fetch(`https://your-cms.com/api/posts/${slug}`, {
next: { revalidate: 3600 } // ISR: 1時間キャッシュ
});
if (!res.ok) return null;
return res.json();
}
// Next.js の generateMetadata と JSON-LD を同じデータソースから生成
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPost(params.slug);
if (!post) return { title: '記事が見つかりません' };
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
siteName: 'AIOGeoScan',
images: [{ url: '/og-image.jpg', width: 1024, height: 1024 }],
publishedTime: post.publishedAt,
authors: [post.author.name],
},
};
}
export default async function BlogPostPage(
{ params }: { params: { slug: string } }
) {
const post = await getPost(params.slug);
if (!post) notFound();
// JSON-LD を動的データから生成
const jsonLd = [
{
'@context': 'https://schema.org',
'@type': 'Article',
'@id': `https://example.com/blog/${params.slug}#article`,
'headline': post.title,
'description': post.excerpt,
'image': post.ogpImage,
'datePublished': post.publishedAt,
'dateModified': post.updatedAt,
'author': {
'@type': 'Person',
'@id': `https://example.com/author/${post.author.slug}#person`,
'name': post.author.name,
'url': `https://example.com/author/${post.author.slug}`,
},
'publisher': { '@id': 'https://example.com/#organization' },
'mainEntityOfPage': {
'@type': 'WebPage',
'@id': `https://example.com/blog/${params.slug}`,
},
'inLanguage': 'ja',
'keywords': post.tags.join(', '),
},
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': [
{ '@type': 'ListItem', 'position': 1, 'name': 'ホーム', 'item': 'https://example.com' },
{ '@type': 'ListItem', 'position': 2, 'name': 'ブログ', 'item': 'https://example.com/blog' },
{ '@type': 'ListItem', 'position': 3, 'name': post.title, 'item': `https://example.com/blog/${params.slug}` },
],
},
];
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\u003c')
}}
/>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
</>
);
}3. TypeScript で JSON-LD を型安全に書く
JSON-LD を素の object 型で書くと、プロパティ名のミスや 必須フィールドの漏れをコンパイル時に検出できません。schema-dts パッケージを使うと、schema.org の型定義を TypeScript で利用でき、 Cursor や Claude Code のコード補完精度も大幅に向上します。
# インストール
npm install schema-dts
# または
pnpm add schema-dts
# --------------------------------------------------------
# 使用例:型安全な Article スキーマの定義
# --------------------------------------------------------
import { Article, WithContext } from 'schema-dts';
const articleJsonLd: WithContext<Article> = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: '記事タイトル',
description: '記事の説明',
datePublished: '2026-04-12',
author: {
'@type': 'Person',
name: '山田 太郎',
},
// 誤ったプロパティ名はTypeScriptエラーになる
// publisedDate: '...' ← コンパイルエラー(typoを検出)
};
// JSX での使用
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(articleJsonLd).replace(/</g, '\u003c')
}}
/>schema-dts の型定義があると、CursorやClaude Codeが「このオブジェクトは Article 型だ」と認識し、 必要なプロパティを正確に補完・提案するようになります。 型定義なしで書くより提案精度が格段に上がるため、 AI開発ツールを使っている場合は特に導入を推奨します。
4. Cursor・Claude Code で JSON-LD を自動生成するプロンプトパターン
AI開発ツールを使ってJSON-LDを生成する際、プロンプトの質が出力品質を大きく左右します。 曖昧な指示では不完全なスキーマが出力されますが、 以下のようなコンテキスト豊富なプロンプトを使うと、すぐに使える完全なコードが生成されます。
プロンプト① Cursor でのコンテキスト指示
@Codebase このプロジェクトはNext.js 14 App Routerを使ったブログサイトです。 app/blog/[slug]/page.tsx に以下の要件でJSON-LDを追加してください: 要件: 1. @type は "Article"(または BlogPosting)を使う 2. headline, description, datePublished, dateModified, author, publisher を含める 3. author は "Person" 型でネストし、name と url を持つ 4. publisher は app/layout.tsx の Organization の @id を参照する形式で書く 5. BreadcrumbList も同じ <script> タグに配列形式で追加する 6. パンくずは「ホーム > ブログ > [記事タイトル]」の3階層 7. データは既存の post オブジェクトから動的に取得する 8. TypeScript で型安全に書く(schema-dts を使用、未インストールなら通常の型でOK) 既存のコードを壊さないように追加してください。
プロンプト② Claude Code での一括実装指示
このNext.js App Routerプロジェクトにサイト全体のJSON-LD構造化データを実装してください。 現在の構成: - app/layout.tsx:ルートレイアウト(全ページ共通) - app/page.tsx:トップページ - app/blog/[slug]/page.tsx:ブログ記事ページ - app/about/page.tsx:会社情報ページ - app/services/[id]/page.tsx:サービス詳細ページ 実装してほしいJSON-LD: 1. layout.tsx → WebSite + Organization(@graph形式) 2. app/page.tsx → WebPage スキーマを追加 3. app/blog/[slug]/page.tsx → Article + BreadcrumbList(動的生成) 4. app/about/page.tsx → Organization + Person(代表者情報) 5. app/services/[id]/page.tsx → Service + BreadcrumbList 制約: - schema.orgの最新仕様に準拠すること - @id によるエンティティの相互参照を使うこと(sameAs / publisher など) - TypeScript の型エラーが出ないこと - Google Rich Results Testでエラーが出ないこと(必須プロパティを漏らさない) - サイトURL は https://example.com を使う(後で置き換えるため) まず実装計画を日本語で説明してから、コードを書いてください。
プロンプト③ GitHub Copilot でのインラインコメント活用
// このページに FAQPage スキーマを追加する // @type: FAQPage // mainEntity: 以下のQ&Aを Question/Answer 形式で記述 // Q1: JSON-LDとは? A: schema.orgを使った構造化データ... // Q2: リッチリザルトが表示されない原因は? A: 主な原因は... // Q3: Next.jsでの実装方法は? A: scriptタグにdangerouslySetInnerHTML... // inLanguage: "ja" // すべてのテキストはプレーンテキストで(HTMLタグなし) const faqJsonLd = // ← ここで Copilot が補完を提案する
5. metadata API と JSON-LD の役割分担(混同しがちなポイント)
Next.js の metadata API(title、description、openGraph など)と JSON-LD はしばしば混同されますが、役割が異なる全く別の技術 です。 両方を正しく実装することが重要です。
| 項目 | Next.js metadata API | JSON-LD |
|---|---|---|
| 出力先 | <title>・<meta> タグ | <script type="application/ld+json"> |
| 主な読み手 | ブラウザ・SNSクローラー(OGP)・検索エンジン(基本情報) | Googleナレッジグラフ・AI検索エンジン・リッチリザルトエンジン |
| リッチリザルト | なし(基本的なタイトル・説明のみ) | あり(FAQ・評価星・パンくずなど) |
| E-E-A-T対応 | 限定的 | 高い(著者・組織の詳細情報を記述可能) |
| AI検索対応 | 部分的(OGPタグを参照することあり) | 高い(構造化された機械可読データとして優先的に利用) |
📌 結論:両方を実装する。どちらか一方では不十分。metadata API で title・description・openGraph を設定し、 さらに JSON-LD でスキーマを実装することが最善策です。 Cursorや Claude Code に依頼するときは「metadata と JSON-LD の両方を実装してください」と 明示するのが重要です(どちらか一方だけ実装する誤りが多い)。
6. 開発環境でのデバッグとローカル確認方法
localhost:3000 を開き、DevToolsの「Elements」タブで <script type="application/ld+json"> を探します。 JSON が正しく出力されているか、改行・エスケープに問題がないかを目視確認します。validator.schema.org では JSON-LD テキストを直接貼り付けて検証できます。 Googleのツールより詳細なエラーメッセージが出るため、 開発中のデバッグに有用です。JSON.stringify(jsonLd, null, 2) の第3引数に 2(インデント幅)を渡すと 整形された JSON が出力され、構造確認が容易になります。 本番環境では第3引数を省略してサイズを最小化します。7. よくある実装ミス:Next.js 固有の落とし穴
NG:Client Component に JSON-LD を置く
'use client' ディレクティブがあるコンポーネントでは、 JSON-LD はクライアントサイドでのみレンダリングされ、 Googleのクローラーが静的 HTML をフェッチした際に存在しない可能性があります。 JSON-LD は必ず Server Component(デフォルト状態)のファイルに書いてください。
NG:JSON.stringify なしで直接オブジェクトを渡す
dangerouslySetInnerHTML に JSON.stringify を省略して オブジェクトをそのまま渡すと [object Object] として出力されます。 必ず { __html: JSON.stringify(jsonLd) } の形式で渡してください。
NG:layout.tsx と page.tsx で同じスキーマを重複させる
Organization スキーマを layout.tsx にも page.tsx にも書くと、 同じページに同じエンティティが2つ存在することになり、Google側で混乱が生じます。 layout.tsx(全ページ共通)と page.tsx(ページ固有)でスキーマの種類を明確に分けてください。
NG:Suspense の外に JSON-LD を置き忘れる
非同期データを使う page.tsx で <Suspense> を使っている場合、 JSON-LD を Suspense の内部に置くと、ストリーミング SSR 時に JSON-LD が 初回 HTML に含まれない可能性があります。 JSON-LD は Suspense の外側(先に出力される部分)に置いてください。
全5章を通して、AI時代に構造化データを使いこなす技術を学びましょう。
実装したJSON-LDの品質を
AIOGeoScanで一括検証できます
Cursor・Claude CodeでJSON-LDを生成した後、AIOGeoScanで検証することで AIが見落としたエラーや必須プロパティの漏れを確実に検出できます。 開発→生成→検証のサイクルを自動化して、構造化データの品質を高速に向上させましょう。
今すぐ自社サイトを無料診断する