構造化データの実装例:Organization・LocalBusiness・BreadcrumbList・ArticleをJSON-LDで書き、公開前後に検証する手順。1か所の設定から生成してページとの食い違いを防ぐ
Organization・LocalBusiness・BreadcrumbList・Articleの
この@id を@id を
この
値は1つの設定ファイルに集め、種類ごとの関数でJSON-LDを作る
構造化データの
このsrc/config/site.ts にsrc/lib/schema.ts の
export const SITE = {
url: 'https://www.yamayamabloglink.com', // canonical・サイトマップ・JSON-LD はすべてこれを使う
name: 'やまやまブログ',
};
export const AUTHOR = { name: '津嘉山 洸', jobTitle: 'CTO' /* ほかにプロフィールURL・SNS */ };
export const COMPANY = { name: '株式会社bundlyze', url: 'https://www.bundlyze.co.jp/' };const abs = (p: string) => new URL(p, SITE.url).href;
export const ORG_ID = `${COMPANY.url}#organization`;
export const PERSON_ID = abs('/about/#person');
export function organization() {
return { '@context': 'https://schema.org', '@type': 'Organization', '@id': ORG_ID, name: COMPANY.name, url: COMPANY.url };
}ページの
URL をabs() でSITE.url から
Organizationは@idを決めて、ほかの構造化データから参照する
Organization は、@id を@id で
この@id を#organization をworksFor から@id の
会社サイトのsameAs などを
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://www.example.co.jp/#organization",
"name": "株式会社サンプル",
"url": "https://www.example.co.jp/",
"logo": "https://www.example.co.jp/images/logo.png",
"address": {
"@type": "PostalAddress",
"postalCode": "530-0000",
"addressRegion": "大阪府",
"addressLocality": "大阪市北区",
"streetAddress": "サンプル1-2-3",
"addressCountry": "JP"
},
"contactPoint": { "@type": "ContactPoint", "telephone": "+81-6-0000-0000", "email": "[email protected]" },
"sameAs": ["https://x.com/example", "https://www.instagram.com/example/"]
}ロゴは、sameAs には、
LocalBusinessは、人を迎える拠点のページに最も具体的な型で書く
LocalBusiness は、Restaurant・HealthClub・DaySpa のように、name と address で、
{
"@context": "https://schema.org",
"@type": "HealthClub",
"@id": "https://www.example.co.jp/studio/umeda/#place",
"name": "サンプルスタジオ 梅田店",
"url": "https://www.example.co.jp/studio/umeda/",
"telephone": "+81-6-0000-0001",
"address": {
"@type": "PostalAddress",
"postalCode": "530-0000",
"addressRegion": "大阪府",
"addressLocality": "大阪市北区",
"streetAddress": "サンプル4-5-6 2階",
"addressCountry": "JP"
},
"geo": { "@type": "GeoCoordinates", "latitude": 34.70245, "longitude": 135.49812 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "opens": "10:00", "closes": "21:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday", "Sunday"], "opens": "09:00", "closes": "18:00" }
],
"parentOrganization": { "@id": "https://www.example.co.jp/#organization" }
}書く
| 項目 | 書き方 |
|---|---|
| 型 | LocalBusiness の |
| 電話番号 | 国番号と+81-6-…) |
| 座標 | 小数点以下 |
| 営業時間 | 24時間表記。00:00 |
| 会社との |
parentOrganization で@id を指す |
拠点が
Articleは記事の型を選び、著者を人として書く
記事のheadline・image・datePublished・dateModified・author などがArticle のBlogPosting、NewsArticle をBlogPosting に
export function article(p) {
return {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: p.title,
url: abs(p.url),
mainEntityOfPage: { '@type': 'WebPage', '@id': abs(p.url) },
datePublished: p.datePublished, // 例 2026-10-03T10:00:00+09:00
dateModified: p.dateModified || p.datePublished,
author: {
'@type': 'Person',
'@id': PERSON_ID,
name: AUTHOR.name, // 名前だけ。肩書きは jobTitle に分ける
url: abs('/about/'),
jobTitle: AUTHOR.jobTitle,
worksFor: { '@type': 'Organization', '@id': ORG_ID, name: COMPANY.name },
sameAs: SAME_AS, // X・Instagram・会社サイトの執筆者ページ
},
publisher: { '@type': 'Person', '@id': PERSON_ID, name: AUTHOR.name },
};
}押さえている
- 著者名に
肩書きや Googleは会社名を 入れない。 author.nameに名前だけを 入れ、 肩書きは jobTitleなどの項目に 分けるよう 説明しています - 著者を
特定できる URLを 付ける。 urlに著者の プロフィールの ページ、 sameAsに本人の SNSを 入れ、 同姓同名と 区別できるようにします - 日時は
タイムゾーン付きで タイムゾーンが書く。 無いと Googlebot 側の タイムゾーンで 解釈されます。 front matter の 日時を +09:00付きで書き、 そのまま ISO 8601 の 文字列に しています
publisher をpublisher は@id を
BreadcrumbListは、画面のパンくずと同じデータから作る
パンくずの
export function breadcrumbs(items: { name: string; url: string }[]) {
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [{ name: 'ホーム', url: '/' }, ...items].map((it, i) => ({
'@type': 'ListItem', position: i + 1, name: it.name, item: abs(it.url),
})),
};
}GoogleのListItem には position・name・item がitem を
検証は、書き出したHTMLの機械チェックとGoogleのツールの2段で行う
構造化データは、
1. 書き出したHTMLの全JSON-LDを、スクリプトで読んで確かめる
静的に
for (const file of htmlFiles) {
const html = fs.readFileSync(file, 'utf8');
for (const m of html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)) {
const d = JSON.parse(m[1]); // 読めなければここで止まる
if (d['@type'] === 'BlogPosting' && !/[+-]\d\d:\d\d$/.test(d.datePublished)) errors.push(`${file}: 日時にタイムゾーンがない`);
if (d['@type'] === 'BreadcrumbList') d.itemListElement.forEach((it, i) => { if (it.position !== i + 1) errors.push(`${file}: パンくずの順番`); });
}
}この
2. Googleの機能の対象になる種類は、リッチリザルト テストで見る
Article・パンくず・LocalBusiness など、
3. それ以外の種類は、Schema Markup Validator で見る
Person・WebSite・ProfilePage のように、@id での
4. 公開後は、URL 検査と種類ごとのレポートで見る
URL 検査の
食い違いが起きやすい所
実装を
| 場面 | 起きること | 防ぎ方 |
|---|---|---|
| 住所・電話・営業時間を |
ページだけ直り、 |
画面と |
| CMSの |
同じ |
書き出した |
| 著者名に |
著者と |
名前と jobTitle を分ける |
| ページに |
ガイドライン違反に |
ページに |
| ホストを |
古いurl や @id に残る |
URLを |
構造化データの
照合に使った資料(Google と schema.org、確認日 2026年10月3日)
- ページに
見えない 内容を 書かない 決まり、 検証に 使う 道具、 違反した ときの 扱い → Google の 構造化データに 関する 一般的な ガイドライン - 置く
ページ、 必須の 項目が 無い こと、 推奨の 項目、 ロゴの 大きさ、 拠点には LocalBusiness の 具体的な 型を 使う こと → Google の 組織情報の マークアップの 解説 - 必須と
推奨の 項目、 具体的な 型、 営業時間・電話番号・座標の 書き方 → Google の ローカル ビジネスの 構造化データの 説明 - 推奨の
項目、 著者の 書き方、 日時の 形式 → Google の 記事の 構造化データの 説明 - 必須の
項目、 最後の 項目の 扱い、 表示の 対象 → Google の パンくずの 構造化データの 説明 - schema.org の
語彙と して 正しいかの 検証 → Schema Markup Validator - 下位の
型と、 parentOrganizationなどの項目 → schema.org の LocalBusiness の 定義
よくある質問
JSON-LDはheadとbodyのどちらに置けばよいですか?
Googleは、
OrganizationとLocalBusinessは両方入れるべきですか?
来店や
リッチリザルト テストで警告が出たら、直さないといけませんか?
エラーは