Skip to content

JSON-LD 怎麼寫?ld+json 的語法、嵌套與驗證流程

JSON-LD 語法與驗證:ld+json 的寫法

JSON-LD(JSON for Linking Data)是撰寫 結構化資料 的建議格式。它的優勢是 與版面完全解耦,標記集中在一個獨立的 <script> 區塊裡,改版面不會動到它。

html
<script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "文章標題",
    "datePublished": "2026-09-07"
  }
</script>

三個關鍵欄位

欄位作用必要性
@context宣告詞彙來自哪一套定義必填
@type這是什麼類型的實體必填
@id這個實體的唯一識別碼選填

@context

值固定是 Schema.org 的網址。它告訴解析器「後面的 headlineauthor 這些詞來自 Schema.org 的定義」。

json
{ "@context": "https://schema.org" }

要用 https 開頭

http://schema.org 仍能解析,但建議統一用 https://schema.org

@type

決定這個實體是什麼,也決定了哪些欄位可用、哪些必填:

json
{ "@type": "Article" }
{ "@type": "Product" }
{ "@type": "BreadcrumbList" }

@id

給實體一個 唯一識別碼,通常用網址。它的用途是讓不同區塊、甚至不同頁面的標記 指向同一個實體

json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "文章標題",
  "publisher": { "@id": "https://example.com/#organization" }
}

@id 讓組織資訊只寫一次

整站的組織資訊在首頁完整描述一次並給它一個 @id,其他頁面用同一個識別碼參照就好,不必每頁重複寫 logosameAs、聯絡資訊。做法見 組織與網站 Organization 標記

巢狀物件

欄位的值可以是另一個實體,這是描述關係的方式:

json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "文章標題",
  "author": {
    "@type": "Person",
    "name": "Away",
    "url": "https://example.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "F2E 前端工程文件",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/site-logo.png"
    }
  }
}

巢狀物件也需要自己的 @type

json
// 錯誤:解析器不知道這是什麼
"author": { "name": "Away" }

// 正確
"author": { "@type": "Person", "name": "Away" }

只有 @context 不必重複,它在最外層宣告一次就涵蓋整段。

陣列的寫法

一個欄位可以有多個值:

json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "image": [
    "https://example.com/16x9.png",
    "https://example.com/4x3.png",
    "https://example.com/1x1.png"
  ],
  "author": [
    { "@type": "Person", "name": "作者一" },
    { "@type": "Person", "name": "作者二" }
  ]
}

單一值也可以寫成陣列

"image": ["url"]"image": "url" 都合法。統一用陣列可以讓程式產生標記時不必判斷數量。

多個區塊 vs @graph

同一頁需要多種型別時,有兩種寫法。

多個獨立區塊

html
<script type="application/ld+json">
  { "@context": "https://schema.org", "@type": "Article", "headline": "..." }
</script>

<script type="application/ld+json">
  { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [] }
</script>

用 @graph 裝在一起

html
<script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@graph": [
      { "@type": "Article", "@id": "https://example.com/page#article", "headline": "..." },
      { "@type": "BreadcrumbList", "itemListElement": [] }
    ]
  }
</script>
多個區塊@graph
可讀性好,一個型別一段較差,全部擠在一起
維護好,可以獨立增減需要動整段
實體互相參照@id 跨區塊參照同一段內直接參照
建議多數情況選這個實體關係複雜時

這個站用多個區塊

每篇文章的檔頭都有 ArticleFAQPage 兩段獨立的區塊,骨幹頁面再各自加上 BreadcrumbListItemList。分開寫的好處正好體現在這裡,多一個型別只是多一段,不必動到既有的標記,也比較容易看出哪一段出問題。

常見語法錯誤

這三個是最常見的

1. JSON 尾隨逗號

JSON 不允許尾隨逗號,整段會解析失敗。這在手寫時特別容易發生。

json
// 錯誤:最後一個欄位後面多了逗號
{
  "@type": "Article",
  "headline": "標題",
}

2. 角括號原字元

json
// 危險:直接寫 HTML 標籤的角括號
"description": "說明 <p> 元素的用法"

角括號寫在 <script> 內容裡時,容易誤觸解析規則。一律改用純名稱敘述:

json
"description": "說明段落元素的用法"

3. 日期格式

json
// 錯誤
"datePublished": "2026/09/07"
"datePublished": "Sep 7, 2026"

// 正確:ISO 8601
"datePublished": "2026-09-07"
"datePublished": "2026-09-07T14:30:00+08:00"

用程式產生而非手寫

手寫容易出錯,而且無法保證與頁面內容一致。正確做法是 從頁面資料產生

js
// 從 frontmatter 產生 Article 標記
function buildArticleSchema(page, domain) {
  return {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: page.title,
    description: page.description,
    image: [`${domain}/images/${page.slug}-og.png`],
    author: { '@type': 'Person', name: 'Away', url: `${domain}/about` },
    datePublished: page.datePublished,
    dateModified: page.dateModified,
    inLanguage: 'zh-Hant-TW',
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': `${domain}/${page.path}`,
    },
  };
}

網域用 token,建置時替換

絕對網址在本機、測試機、正式機都不同。在標記裡放一個 token,建置時再換掉:

js
const SITE_TOKEN = /%SITE%/g;
const replaceSiteToken = (value) =>
  typeof value === 'string' ? value.replace(SITE_TOKEN, domain) : value;

替換函式要能遞迴處理巢狀物件與陣列,否則深層的網址會漏掉。

SPA 的動態注入

風險比伺服器端渲染高

搜尋引擎渲染頁面後讀得到動態注入的標記,所以 技術上有效。但有兩個風險:

  1. 渲染有延遲:排在佇列裡,通常幾秒,但沒有保證。
  2. 程式碼出錯就全沒了:靜態寫在 HTML 裡的標記不會有這個問題。

最穩的做法是在 伺服器端渲染或建置階段 就寫進 HTML。

若只能動態注入,注意這些:

js
// 在頁面渲染完成前注入,並記得路由切換時清掉舊的
function injectSchema(data) {
  const existing = document.querySelector('script[data-schema="page"]');
  if (existing) existing.remove();

  const script = document.createElement('script');
  script.type = 'application/ld+json';
  script.dataset.schema = 'page';
  script.textContent = JSON.stringify(data);
  document.head.appendChild(script);
}

務必用 網址檢查工具 確認渲染後真的讀到了。 做法見 SPA 動態更新 title 與 meta

驗證流程

順序工具看什麼
1Schema Markup Validator純語法與型別是否合法
2複合式搜尋結果測試能否取得複合式結果、缺哪些欄位
3Search Console 強化報告全站範圍的錯誤統計

前兩者測單頁、給即時結果;第三者看全站、有延遲但能發現整批錯誤。

自動化的部分可以在建置後掃一遍產出的 HTML:

js
// 抽出所有 ld+json 做 JSON 解析與必填欄位檢查
const blocks = html.matchAll(
  /<script type="application\/ld\+json">([\s\S]*?)<\/script>/g,
);

for (const [, json] of blocks) {
  const data = JSON.parse(json); // 語法錯誤會在這裡拋出
  if (!data['@context'] || !data['@type']) {
    throw new Error('缺少 @context 或 @type');
  }
}

檢查清單

項目標準
@context 且值為 Schema.org 的網址必備
每個實體都有 @type必備
JSON 語法正確(無尾隨逗號)必備
沒有角括號原字元必備
日期符合 ISO 8601必備
網址是絕對網址必備
標記內容與頁面實際內容一致必備
由程式產生而非手寫建議
通過語法驗證與複合式結果測試建議

常見問題

一頁可以放幾個 ld+json 區塊?

沒有數量限制,多個區塊會被合併理解。實務上按型別分開寫比較好維護,例如:文章一個區塊、麵包屑一個區塊。需要讓多個實體互相參照時,才改用 @graph 把它們裝在同一個區塊裡。

@context 一定要寫嗎?

一定要,而且值固定是 Schema.org 的網址。它宣告後面用的詞彙來自哪一套定義,少了它整段標記就無法解析。要注意用的是 https 開頭的版本。

@id 是做什麼的?

它給實體一個唯一識別碼,讓不同區塊或不同頁面的標記可以指向同一個實體。例如:整站的組織資訊只在首頁完整描述一次,其他頁面用同一個識別碼參照它,就不必重複寫全部欄位。

最常見的語法錯誤是什麼?

三個:JSON 尾隨逗號、把角括號原字元直接寫進字串裡、以及日期格式不符 ISO 8601。前兩者會讓整段標記解析失敗,第三者會讓那個欄位被忽略。

動態注入的結構化資料有效嗎?

搜尋引擎渲染頁面後讀得到就有效,但風險較高:渲染有延遲、程式碼出錯就全沒了。最穩的做法是在伺服器端渲染或建置階段就寫進 HTML。若只能動態注入,務必用網址檢查工具確認渲染後真的讀到了。

延伸閱讀

參考資料:JSON-LD 1.1 規格