t28.dev

このウェブページ (t28.dev) の構造化データを考える

2026/1/24に公開
Tech

構造化データの基礎」を書いたことで構造化データを完全に理解した 1 ので、早速、このウェブページ (t28.dev) の構造化データを考える。

調べたこと・決めたことまとめ

構造化データを考えるなかで、調べたこと・決めたことをまとめておく。

@graph を使う

JSON-LD は

  • 単一の node object
  • @context と/または @graph のみを含むマップ
  • node object の配列

いずれかの形式で記述する。

A JSON-LD document MUST be a single node object, a map consisting of only the entries @context and/or @graph, or an array of zero or more node objects.

ref: 9. JSON-LD Grammar

仕様上、推奨されている形式はない。 複数の node object を含む場合、 @graph によって @context を共有できるので便利なようだ。

This mechanism can be useful when a number of nodes exist at the document’s top level that share the same context, which is, e.g., the case when a document is flattened. The @graph keyword collects such nodes in an array and allows the use of a shared context.

{
  "@context": {
    "@vocab": "http://xmlns.com/foaf/0.1/"
  },
  "@graph": [
    {
      "@id": "http://manu.sporny.org/about#manu",
      "@type": "Person",
      "name": "Manu Sporny"
    },
    {
      "@id": "https://greggkellogg.net/foaf#me",
      "@type": "Person",
      "name": "Gregg Kellogg"
    }
  ]
}

ref: 4.9 Named Graphs

@id で fragment を使う

JSON-LD において @id (IRI) による node object の識別は必須ではない。 しかし node object が別の node object を参照する場合、@id を指定する必要がある。

To be able to externally reference nodes in an RDF graph, it is important that nodes have an identifier.

ref: 3.3 Node Identifiers

「私」を表現する node object を https://schema.org/Person type で作りたいが、https://t28.dev にプロフィールページはない。 そのため、プロフィール情報が含まれるトップページの URL を使って IRI を定義することにした。

{
  "@type": "Person",
  "@id": "https://t28.dev/#person"
}

IRI には fragment を含めてよい。

IRI: The absolute form of an IRI containing a scheme along with a path and optional query and fragment segments.

ref: 1.4 Terminology

また IRI は URL (Web 上のリソースを特定する)ではなくリソースを識別するものであるため、 逆参照2 は必須ではない (できるに越したことはないけど)。

the primary distinction is that a URL locates a resource on the web, an IRI identifies a resource. While it is a good practice for resource identifiers to be dereferenceable, sometimes this is not practical.

ref: 3.2 IRIs

そのため対応した anchor をHTML 内に設置せず、https://schema.org/Person の IRI として https://t28.dev/#person を宣言する。

ページをまたいだ node reference を使わない

node reference@id のみを持つ node object で、他の node object を参照するために使う。 重複した宣言が減るので便利な仕組みではある。

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Person",
      "@id": "https://t28.dev/#person",
      "name": "YAMAMOTO Tatsuya"
    },
    {
      "@type": "WebSite",
      "name": "t28.dev",
      "url": "https://t28.dev/",
      // 👇️ node reference
      "publisher": { "@id": "https://t28.dev/#person" }
    }
  ]
}

しかし構造化データの読み手の Google Search を考慮すると、少々不安になる。 クローラーが JSON-LD をどのように評価するか明確ではなく、node reference をページ外の node object を参照した上で評価してくれるかが分からない。 下記など、ガイドラインからは「情報はページ内で完結させてね」と読めなくも…ない。

場所

  • ドキュメントで特に指定されていない限り、構造化データは、内容を記述するページに実装します。
  • コンテンツが同一の重複するページがある場合は、正規ページだけでなく、重複するすべてのページに同じ構造化データを実装することをおすすめします。

ref: 構造化データに関する一般的なガイドライン

そのため無難に node reference を使わず、同じ IRI の node object を都度宣言することにする。 このサイトは Astro で SSG しており JSON-LD も TypeScript の変数から構築するので、冗長な宣言の辛さはそこまで大きくないはず。

WebSiteWebPage の違い

https://schema.org/WebSiteWeb ページの集合体のこと。 一方、https://schema.org/WebPage は Webページのこと

  • https://t28.dev/**/* (ページ全体) を表現する WebSite
  • https://t28.dev/ (単一ページ) を表現する WebPage

役割が違うので、トップページでは両方の node object を宣言する。

意外とこの2つの違いを即答するのは難しいかも。 Schema.org による語彙の定義の重要性を感じる。

CollectionPageBlog どちらを使うか

https://schema.org/CollectionPageWebPage を継承した、より具体的な型

実態として、現状のトップページにはブログ記事の一覧しかないので https://schema.org/Blog が良さそうな気もする。 しかし他の種類のコンテンツを含める可能性がない…とは言い切れない 3 ので、「リンクされたコンテンツ (https://schema.org/CreativeWorks) の集合」として汎用性を持たせておく。 https://t28.dev/blog が出来たら、そこには Blog を使うと良さそう。

t28.dev のページ構造

https://t28.dev 配下には 4 種類のページがある。それぞれの構造化データを考える。

ページ名URL
トップページhttps://t28.dev
ブログ記事ページhttps://t28.dev/blog/fundamentals-of-structured-data
s-works トップページhttps://t28.dev/s-works
s-works 成果紹介ページhttps://t28.dev/s-works/achievement/lovelive-days-5th-anniversary

トップページ

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Person",
      "@id": "https://t28.dev/#person",
      // Google required properties
      // https://developers.google.com/search/docs/appearance/structured-data/profile-page?hl=ja#profile-target-specification
      "name": "YAMAMOTO Tatsuya",
      // Google recommended properties
      // https://developers.google.com/search/docs/appearance/structured-data/profile-page?hl=ja#profile-target-specification
      "alternateName": ["@T28_tatsuya"],
      "description": "LLer and programmer.",
      "image": "https://t28.dev/profile.jpg",
      "sameAs": [
        "https://twitter.com/T28_tatsuya",
        "https://x.com/T28_tatsuya",
        "https://github.com/TatsuyaYamamoto"
      ]
      // other properties
    },
    {
      "@type": "WebSite",
      "@id": "https://t28.dev/#website",
      // Google required properties
      // https://developers.google.com/search/docs/appearance/site-names?hl=ja#website
      "name": "t28.dev",
      "url": "https://t28.dev/",
      // Google recommended properties
      // https://developers.google.com/search/docs/appearance/site-names?hl=ja#website
      // "alternateName": [],
      // other properties
      "publisher": {
        "@id": "https://t28.dev/#person"
      },
      "description": "@T28_tatsuyaが、あれこれ書いている。"
    },
    {
      "@type": "CollectionPage",
      "@id": "https://t28.dev/#collectionpage",
      "name": "t28.dev",
      "url": "https://t28.dev/",
      "publisher": {
        "@id": "https://t28.dev/#person"
      },
      "mainEntity": {
        "@type": "ItemList",
        // Google required properties
        // https://developers.google.com/search/docs/appearance/structured-data/recipe?hl=ja#item-list
        "itemListElement": [
          {
            "@type": "ListItem",
            // required
            "position": 1,
            "url": "https://t28.dev/blog/fundamentals-of-structured-data",
            // others
            "item": {
              "@type": "BlogPosting",
              "url": "https://t28.dev/blog/fundamentals-of-structured-data",
              "headline": "構造化データの基礎",
              "datePublished": "2026-01-04",
              "dateModified": "2026-01-05"
            }
          }
          // 以降、記事のリスト...
        ]
      }
    }
  ]
}

ブログ記事ページ

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Person",
      "@id": "https://t28.dev/#person",
      // Google required properties
      // https://developers.google.com/search/docs/appearance/structured-data/profile-page?hl=ja#profile-target-specification
      "name": "YAMAMOTO Tatsuya",
      // Google recommended properties
      // https://developers.google.com/search/docs/appearance/structured-data/profile-page?hl=ja#profile-target-specification
      "alternateName": ["@T28_tatsuya"],
      "description": "LLer and programmer.",
      "image": "https://t28.dev/profile.jpg",
      "sameAs": [
        "https://twitter.com/T28_tatsuya",
        "https://x.com/T28_tatsuya",
        "https://github.com/TatsuyaYamamoto"
      ]
      // other properties
    },
    {
      "@type": "WebPage",
      "@id": "https://t28.dev/blog/fundamentals-of-structured-data#webpage",
      "url": "https://t28.dev/blog/fundamentals-of-structured-data",
      "mainEntity": {
        "@id": "https://t28.dev/blog/fundamentals-of-structured-data#blogposting"
      },
      "publisher": {
        "@id": "https://t28.dev/#person"
      }
    },
    {
      "@type": "BlogPosting",
      "@id": "https://t28.dev/blog/fundamentals-of-structured-data#blogposting",
      "url": "https://t28.dev/blog/fundamentals-of-structured-data",
      // Google recommended properties
      // https://developers.google.com/search/docs/appearance/structured-data/article#article-types
      "author": {
        "@id": "https://t28.dev/#person"
      },
      "headline": "構造化データの基礎",
      "datePublished": "2026-01-04",
      "dateModified": "2026-01-05",
      "image": "https://t28.dev/blog/fundamentals-of-structured-data.ogp.png",
      // other properties
      "mainEntityOfPage": {
        "@id": "https://t28.dev/blog/fundamentals-of-structured-data#webpage"
      },
      "articleSection": "Tech",
      "inLanguage": "ja",
      "description": "著者の学習メモとしての構造化データ入門。JSON-LD を使う理由や基本的な書き方を説明しています。"
    }
  ]
}

s-works トップページ

また、こんど…😅

s-works 成果紹介ページ

これも、またそのうちで…😂

Footnotes

  1. https://www.google.com/search?q=%E3%83%80%E3%83%8B%E3%83%B3%E3%82%B0%E3%82%AF%E3%83%AB%E3%83%BC%E3%82%AC%E3%83%BC%E5%8A%B9%E6%9E%9C

  2. HTTP Get が出来て、HTML 内に anchor がある

  3. 多分、ないけど。