schema 巢狀串接——用 @id 把結構化資料連成圖

在 WordPress 後台裝好 SEO 外掛、文章也填了標題與摘要,用 Google 複合式搜尋結果測試工具一驗,卻只看到一堆彼此獨立的結構化資料區塊:麵包屑是麵包屑、文章是文章、組織是組織,三者各說各話,搜尋引擎讀不出「這篇文章屬於哪個網站、由哪個組織發布、在網站結構的哪個位置」。這正是 schema 巢狀串接要解決的問題。把麵包屑、文章、組織這幾個結構化資料節點,用 @id 互相指向,串成一張連通的圖(graph),搜尋引擎才能一次看懂整頁的實體關係,而不是把每塊資料當成孤島。

這篇文章帶你把零散的 JSON-LD 接成一張 @graph:先講清楚 @id 到底在做什麼、巢狀(nesting)跟串接(referencing)差在哪,再給一份可以直接改用的完整範例,最後說明在 WordPress 環境下,外掛已經幫你產出的圖該怎麼接手,以及驗證時哪些錯誤工具不會幫你抓。

@id 在 JSON-LD 裡到底扮演什麼角色

@id 是給結構化資料節點掛上的唯一識別碼,讓其他節點可以指名引用它,而不必把整段內容重抄一遍。它是 JSON-LD 內部的參照系統,跟用來告訴搜尋引擎網址的 url 屬性不是同一回事。一個只有名字的字串,搜尋引擎讀完就過去了;掛上 @id 之後,這個節點就變成圖裡一個可以被反覆指向的實體。

值通常寫成「網域加井字號片段」的形式,例如 https://ezwps.com/#organization。要注意這串值不需要真的對應到一個能打開的網頁,它只是個識別碼,唯一且前後一致才是重點。同一個組織,在站內任何一頁被引用時都用同一個 @id 字串,搜尋引擎才會把它們當成同一個實體。

片段命名要有意義。用 #organization#article#breadcrumb 這種看得懂的描述,遠比 #id1#node2 好維護,日後除錯也快。一個實體只配一個 @id,不要讓兩個不同實體共用同一串值,否則它們會被合併成一個語意混亂的節點。

巢狀與 @id 串接是兩種不同的組裝方式

巢狀指的是把一個實體直接寫進另一個實體的屬性裡,形成階層;串接則是用 @id 指向別處已經定義好的實體。兩者都能表達關係,差別在於資料要不要重複。

巢狀的寫法很直覺。文章的作者是一個人,就把 Person 物件整段塞進 author 屬性;發布者是組織,就把 Organization 整段塞進 publisher。屬性的值是「一個東西」而不只是名字時,就用帶有自己 @type 的物件去填,搜尋引擎才有實體可以解析,而不是拿到一個無法連結的純文字。

巢狀的真正限制不是深度,而是重複。當同一個組織同時出現在文章的 publisher、又出現在作者的 worksFor,你就在兩個地方維護同一份資料。改了一處忘了另一處,整頁的結構化資料就自相矛盾。這時候 @id 串接就派上用場:組織只完整定義一次並掛上 @id,其他需要指向它的地方,只寫一個 { "@id": "..." } 的參照就好。

判斷原則很簡單。單純的階層關係(地址放進在地商家、評分放進產品)用巢狀就夠;當頁面上有多個彼此交叉引用的實體,就改用 @id 串接,把它們收進同一張 @graph

為什麼要把多個節點收進一個 @graph

@graph 是一個陣列,把同一頁的多個實體裝在同一個 JSON-LD 區塊裡,共用最上層的一個 @context。當這些實體彼此用 @id 交叉引用時,這些參照會在同一張圖裡解析,省去重複定義,關係也更清楚。

你其實有兩種寫法可以選。一種是寫多個 <script type="application/ld+json"> 區塊,每個區塊各自帶 @context、各自獨立;另一種是用單一區塊,裡面放一個 @graph 陣列裝進多個實體。Google 兩種都吃,效果沒有高下之分。

選擇的依據在於實體之間有沒有互相引用。各實體彼此獨立、不需要互指時,分開的 script 區塊就夠用。但麵包屑、文章、組織這種典型組合,文章要指向發布它的組織、頁面要指向所屬的網站、麵包屑要掛在頁面底下,彼此交織,這時把它們收進一個 @graph 最乾淨。所有跨節點的 @id 參照都在同一張圖內解析,不會散落在好幾個區塊裡難以追蹤。

下面這張圖示意一頁文章頁裡,各節點如何透過 @id 互相指向:

WebSite(網站)
#website
WebPage(頁面)
#webpage.isPartOf 指向 #website

BreadcrumbList
被 #webpage 的 breadcrumb 指向
Article(文章)
mainEntityOfPage 指向 #webpage
Organization(組織)
被 Article 的 publisher 指向

一份把麵包屑、文章、組織串起來的完整範例

下面這份 @graph 把一頁部落格文章頁需要的五個節點接成一張圖,每個跨節點的指向都用 @id。看的時候重點放在那些只寫 { "@id": "..." } 的地方,那就是串接發生的位置。

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://ezwps.com/#organization",
      "name": "EZWPS",
      "url": "https://ezwps.com/",
      "logo": {
        "@type": "ImageObject",
        "url": "https://ezwps.com/logo.png"
      },
      "sameAs": [
        "https://www.facebook.com/ezwps",
        "https://www.youtube.com/@ezwps"
      ]
    },
    {
      "@type": "WebSite",
      "@id": "https://ezwps.com/#website",
      "url": "https://ezwps.com/",
      "name": "EZWPS",
      "publisher": { "@id": "https://ezwps.com/#organization" }
    },
    {
      "@type": "WebPage",
      "@id": "https://ezwps.com/nested-schema-graph/#webpage",
      "url": "https://ezwps.com/nested-schema-graph/",
      "name": "麵包屑、文章、組織 schema 巢狀串接",
      "isPartOf": { "@id": "https://ezwps.com/#website" },
      "breadcrumb": { "@id": "https://ezwps.com/nested-schema-graph/#breadcrumb" },
      "primaryImageOfPage": { "@id": "https://ezwps.com/nested-schema-graph/#primaryimage" }
    },
    {
      "@type": "BreadcrumbList",
      "@id": "https://ezwps.com/nested-schema-graph/#breadcrumb",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "首頁",
          "item": "https://ezwps.com/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "SEO 優化",
          "item": "https://ezwps.com/category/seo/"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "schema 巢狀串接"
        }
      ]
    },
    {
      "@type": "Article",
      "@id": "https://ezwps.com/nested-schema-graph/#article",
      "headline": "麵包屑、文章、組織 schema 巢狀串接",
      "datePublished": "2026-06-14T08:00:00+08:00",
      "dateModified": "2026-06-14T08:00:00+08:00",
      "mainEntityOfPage": { "@id": "https://ezwps.com/nested-schema-graph/#webpage" },
      "author": {
        "@type": "Person",
        "@id": "https://ezwps.com/author/editor/#person",
        "name": "EZWPS 編輯部",
        "url": "https://ezwps.com/author/editor/"
      },
      "publisher": { "@id": "https://ezwps.com/#organization" }
    }
  ]
}

幾個串接的關鍵點值得逐條看。Organization 只在頂端完整定義一次,後面 WebSite 的 publisher 跟 Article 的 publisher 都只用 @id 指回它,組織資料因此只有一份。WebPage 用 isPartOf 把自己掛到 WebSite 底下,再用 breadcrumb 屬性指向 BreadcrumbList 節點。Article 則用 mainEntityOfPage 反向指回 WebPage,等於告訴搜尋引擎「這篇文章就是這個頁面的主體」。

麵包屑的最後一層通常不放 item 網址,因為它代表目前所在的頁面,沒有「再點下去」的目標。position 從 1 開始照層級遞增,名稱要跟頁面上實際顯示的麵包屑文字一致,不要為了塞關鍵字而寫得跟畫面不同。

麵包屑為什麼掛在 WebPage 底下而不是獨立放著

麵包屑在語意上跟其他節點性質不同。文章、組織、產品這些節點是在描述頁面「內容的意義」,而麵包屑描述的是「導覽位置」,是網站結構而非內容本身。因此一般情況下不會把 BreadcrumbList 巢狀塞進 Article 或 Organization 裡,那會混淆兩種不同層級的資訊。

但 WebPage 是例外。WebPage 型別本身就有一個專門的 breadcrumb 屬性,設計上就是用來掛麵包屑的。把 BreadcrumbList 定義成獨立節點、再讓 WebPage 用 breadcrumb 透過 @id 指向它,既符合語意,又能讓整張圖維持連通,是目前主流外掛採用的做法。

換句話說,麵包屑不該被硬塞進文章節點,但也不必完全孤立。讓它跟 WebPage 透過 @id 連起來,是最貼近 schema.org 設計意圖的位置。

跨頁引用同一個實體時 @id 與 url 要一起用

很多人以為只要在每一頁都寫同一個 @id,搜尋引擎就會自動把跨頁的資料合併。實情並非如此。Google 是逐頁處理結構化資料的,即使你在不同頁面用了相同的 @id,它也不會主動把這些資訊串成同一個實體。

跨頁要建立可靠的連結,正確做法是 @id 搭配 url 一起出現。@id 負責提供穩定唯一的識別,url 則告訴搜尋引擎這個實體的「家」在哪裡。組織節點寫上 "url": "https://ezwps.com/",產品節點寫上它自己的網址,搜尋引擎才有辦法在站內不同頁面間建立同一實體的連結。

還有一個容易被忽略的前提:因為搜尋引擎逐頁處理,每一頁的結構化資料都要能獨立成立。即使某個實體是用 @id 從別頁參照過來的,這一頁也要把必要屬性補齊,或用 @graph 把完整定義帶在同頁。光丟一個 @id 參照、本頁卻沒有任何可解析的內容,那個參照對搜尋引擎來說等於懸空。傳統的 HTML 連結也別省,組織有獨立頁面時,從相關頁面用一般超連結指過去,能進一步強化這層關係。

驗證時哪些 @id 錯誤工具不會幫你抓

寫完一定要驗證,但要先認清驗證工具的盲點。Google 複合式搜尋結果測試與 schema.org 驗證器都是逐頁分析,它們會抓語法錯誤、會告訴你某個型別能不能拿到複合式結果,但不會偵測 @id 參照是否真的接得上。跨頁的 @id 對不上、或本頁參照了一個沒定義的 @id,這類問題連 Screaming Frog 這種爬蟲工具也未必抓得到,得靠你自己核對。

有幾類錯誤特別常見,值得收稿前逐項自查。JSON 不允許尾隨逗號,在最後一個屬性後面多打一個逗號,整段就會解析失敗,這在 JavaScript 裡合法、在 JSON 裡卻是致命傷。@id 撞號是另一個常犯的錯,兩個不同實體共用同一串 @id,會被併成一個語意含糊的節點,所以片段要用 #organization#article 這種能區分的描述。還有自創屬性,schema.org 沒有的屬性會被 Google 默默忽略,不會報錯但也沒作用,寫之前查一下該型別到底有哪些合法屬性。

最後一條紅線是「標記必須對應頁面上看得到的內容」。麵包屑名稱要跟畫面顯示一致、文章標題要跟實際標題相同。為了 SEO 在結構化資料裡塞畫面上根本沒有的資訊,違反 Google 的政策,得不償失。驗證流程建議兩個工具都跑:複合式搜尋結果測試確認能不能拿到 rich result,schema.org 驗證器則能驗到更多非 rich result 的型別,也比較容易看出各節點是怎麼接在一起的。

WordPress 環境下這張圖通常已經幫你產好了

如果你的站跑在 WordPress 上,多數主流 SEO 外掛已經自動輸出一張串好的 @graph,不必從零手刻。Yoast 就是典型例子:它把 WebPage 當成中樞,自動接上 isPartOf 指向 WebSite、breadcrumb 指向 BreadcrumbList、主圖透過 primaryImageOfPage 指向 ImageObject,文章與發布它的組織也都用 @id 串在同一張圖裡。Rank Math 等外掛的邏輯也類似。

這帶出一個 WordPress 站最該注意的問題:不要疊床架屋。同時啟用兩個都會輸出結構化資料的外掛、或外掛之外又用主題功能、再加一段手刻 JSON-LD,頁面上就會冒出多張各自為政、甚至彼此衝突的圖,組織可能被宣告兩次、@id 也對不齊。先用驗證器看清楚目前頁面已經輸出了什麼,再決定是沿用外掛產出、還是關掉外掛的某項輸出自己接管,避免重複。

需要手動延伸時,盡量沿用外掛已經建立的 @id。想替文章補上產品或服務的關聯,就讓你的節點用 @id 指向外掛已經產出的 WebPage 或 Organization,而不是另外宣告一個新的組織節點。沿用既有 @id、讓新節點掛進同一張圖,遠比另起爐灶乾淨,也省去日後維護兩套識別碼的麻煩。

維護同樣不能鬆手。頁面內容、外掛版本、Google 對結構化資料的要求都會變動,圖一旦跟頁面實際內容脫節(也就是 schema drift),輕則失去複合式結果資格,重則被判定標記與內容不符。改版、換主題、調整分類層級時,順手把 schema 重新驗一次,是低成本的保險。

從零散區塊到一張連通的圖,該怎麼動手

schema 巢狀串接的核心,是用 @id 把麵包屑、文章、組織這些原本各自獨立的節點接成一張搜尋引擎能一次讀懂的圖。組織只定義一次、其他節點用 @id 指回;WebPage 當中樞,往上用 isPartOf 接網站、往旁用 breadcrumb 接麵包屑、文章再用 mainEntityOfPage 指回頁面。跨頁引用記得 @idurl,每一頁的標記都要能獨立成立。

動手的順序是:先用驗證器看清楚現有頁面已經輸出了什麼,WordPress 站多半已有外掛產好的圖;確認沒有重複的圖之後,再決定沿用或接手;需要延伸時沿用既有 @id,最後用複合式搜尋結果測試與 schema.org 驗證器各跑一次。把這套節奏走順,零散的結構化資料就能變成一張連通、可維護、搜尋引擎讀得懂關係的實體圖。

相關文章
標籤: WordPress SEO, Schema, 結構化資料, JSON-LD, 麵包屑