EP.03 で と を扱いました。この回はその運用編です。書いたあと、どう維持するか。
結論から言うと、手書きで維持し続けるのは不可能です。ページ数が増え、内容が更新されるようになった時点で、必ずずれます。
1. なぜ必ず腐るのか
構造化データは、本文と同じ情報を、別の場所に別の形式で書いたものです。同じ情報が2箇所にある以上、片方だけ更新されるのは時間の問題です。
| 更新される場所 | 更新されない場所 | 結果 |
|---|---|---|
| 本文の価格 | 構造化データの価格 | 古い価格が機械に読まれる |
| 本文の営業時間 | 構造化データの営業時間 | 誤った時間が案内される |
| 記事の更新日 | 構造化データの日付 | 鮮度の判断がずれる |
| 著者の肩書 | 構造化データの著者情報 | 信頼性の評価がずれる |
1行目が最も実害が大きい。人が見る画面には新しい価格が出ているのに、機械が読む部分には古い価格が残っている。生成AIが古い価格で回答すれば、問い合わせの現場で齟齬が起きます。
構造化データが無ければ、機械は本文を読みます。ずれたものがあると、機械はそちらを優先する可能性があります。「念のため付けておく」つもりが、誤情報を配信する経路になりうるわけです。
2. 本文から生成する
対策は単純で、同じ情報を2箇所に書かないことです。本文のデータから構造化データを生成すれば、ずれようがありません。
type Article = { slug: string; title: string; excerpt: string; date: string; dateModified?: string; author: { name: string; url: string };};
/** 記事データから JSON-LD を生成する。手で書かない。 */export function articleJsonLd(a: Article, siteUrl: string) { const url = `${siteUrl}/blog/${a.slug}`; return { "@context": "https://schema.org", "@type": "Article", headline: a.title, // 本文と同じ値を使う description: a.excerpt, // ここも同じ datePublished: a.date, dateModified: a.dateModified ?? a.date, author: { "@type": "Person", name: a.author.name, url: a.author.url, }, mainEntityOfPage: { "@type": "WebPage", "@id": url }, };}要点は、`headline` に `a.title` を渡していることです。タイトルを変更すれば構造化データも自動的に変わります。ここに文字列を直接書くと、その瞬間から腐り始めます。
元データが1つであれば、更新漏れという概念自体がなくなります。これは 壊れないデータ基盤の作り方 で扱う「同じ情報を2箇所に持たない」という原則と、まったく同じ話です。データ基盤の設計原則が、そのまま の運用にも当てはまるわけです。同じ問題は、領域を変えて何度も現れます。
3. 生成できない場合
現実には、生成できない場面もあります。本文が手書きの HTML だったり、元データが構造化されていない場合です。
| 状況 | 対処 |
|---|---|
| 記事データが構造化されている | 生成する(上の形) |
| 本文がマークダウン | 先頭のメタ情報から生成する |
| 手書きHTML、ページ数が少ない | 手書きでよいが、検証を入れる |
| 手書きHTML、ページ数が多い | まず元データの構造化から |
最下行は、構造化データの話を超えた課題です。ページごとに内容が手書きで散らばっている状態では、構造化データ以前に更新自体が大変なはずです。そちらを先に解決するほうが、結果的に早い。逆に言えば、構造化データを入れようとして初めて、元データが構造化されていないことに気づくというのはよくある流れです。それ自体が有益な発見だと考えてください。
4. 形式ではなく、値を検証する
検証というと、形式が正しいか(構文エラーがないか、必須項目が揃っているか)の確認を思い浮かべます。それも必要ですが、本当に効くのは値の検証です。
| 検証の種類 | 見つかるもの | 見つからないもの |
|---|---|---|
| 形式の検証 | 構文エラー、項目の欠落 | 値が本文とずれている |
| 値の検証 | 本文との不一致 | — |
形式が正しくても、中身が本文と違っていれば意味がありません。むしろ形式が正しいぶん、機械は素直に読み込みます。
import { describe, it, expect } from "vitest";
describe("構造化データが本文と一致していること", () => { it("タイトルが一致する", () => { const article = getArticle("llmo-09-structured-data-ops"); const jsonLd = articleJsonLd(article, "https://example.com"); expect(jsonLd.headline).toBe(article.title); });
it("更新日が本文の更新日以降である", () => { const article = getArticle("llmo-09-structured-data-ops"); const jsonLd = articleJsonLd(article, "https://example.com"); expect(new Date(jsonLd.dateModified).getTime()) .toBeGreaterThanOrEqual(new Date(article.date).getTime()); });
it("全記事で必須項目が欠けていない", () => { for (const article of getAllArticles()) { const jsonLd = articleJsonLd(article, "https://example.com"); expect(jsonLd.headline, `${article.slug}: headline が空`).toBeTruthy(); expect(jsonLd.author.name, `${article.slug}: author が空`).toBeTruthy(); expect(jsonLd.datePublished, `${article.slug}: 日付が空`).toBeTruthy(); } });});3番目のように全記事に対して回すのが重要です。1ページだけ確認しても、他のページがずれていれば同じことです。LLM時代のテスト戦略 で扱った考え方が、そのまま使えます。失敗したときにどのページか分かるメッセージを添えておくのも要点で、これがないと数百件の中から探すことになります。
5. 公開前に止める
検証は、公開前に自動で走るようにします。公開後に気づく仕組みでは、その間ずれた情報が配信され続けます。
- 1変更のたびに検証を回す — 手動確認にしない
- 2失敗したら公開を止める — 警告だけでは無視される
- 3新しいページも自動的に対象になる — 一覧に追加し忘れない形にする
- 4外部の検証ツールも定期的にかける — 仕様の変更に気づける
3番目が地味に効きます。検証対象を手で列挙する方式にすると、新しいページを追加したときに登録し忘れます。全記事を自動的に走査する形にしておいてください。
4番目は、こちらのコードが正しくても仕様のほうが変わることへの備えです。推奨される項目が増えたり、扱いが変わったりします。定期的に外部のツールで確認すると気づけます。自前の検証は自分が想定した範囲しか見ないので、外部の目を入れる意味がここにあります。
6. どこまでやるかの線引き
構造化データは、種類を増やそうと思えばいくらでも増やせます。ただし増やすほど維持の対象も増えます。
| 対象 | 優先度 | 理由 |
|---|---|---|
| 記事・ページの基本情報 | 高い | 全ページに関わる |
| 組織・著者の情報 | 高い | 信頼性の判断に効く(EP.04) |
| よくある質問 | 中 | 回答として引用されやすい |
| 価格・在庫 | 慎重に | ずれたときの実害が大きい |
| 細かい属性の網羅 | 低い | 維持の負担に見合わない |
4行目は入れるべきだが、生成と検証が確実にできる場合に限るという位置づけです。手書きで価格を書くくらいなら、書かないほうが安全です。誤った価格が機械可読な形で流れるより、本文だけを読ませたほうがよい。
構造化データは同じ情報を2箇所に持つため、必ず腐る。対策は本文のデータから生成すること。検証は形式ではなく値を見る(本文と一致しているか)。全記事に対して自動で回し、失敗したら公開を止める。そして維持できない項目は、書かないほうが安全です。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。