修好 Rank Math 重複 Schema 警告:1 行 filter 解 Search Console 報錯

打開 Google Search Console,看到「重複的結構化資料」警告,點進去全是 Rank Math 吐出來的 Article 或 WebPage Schema。同一個頁面被標了兩次、三次,Google 爬蟲開始抱怨欄位衝突、缺少全域識別碼。你心想:不是開了外掛就該全自動嗎?怎麼反而越補越多洞。

這個坑的背景很單純:Rank Math 的 Schema 模組為了相容各種佈景主題與自訂內容類型,預設行為傾向「能加就加」。它不會主動檢查主題是否已經輸出了 HTML 語意標籤、或是頁面編輯器偷偷塞了一層 JSON-LD。當多個來源同時宣告同一頁面是 Article 或 WebPage,重複錯誤就出現了。下面這組 filter 就是為了解決這個問題,一次把多餘的 Schema 節點清乾淨,而且只針對 Page 類型下手,不影響文章、商品或任何自訂 post type。

為什麼 Rank Math 會輸出重複 Schema

Rank Math 在 wp_head 階段會透過自己的 JSON-LD 引擎,根據當前查詢物件動態組裝 Schema 陣列。它會先檢查頁面類型,然後依序附加 Organization、WebSite、WebPage 等基本節點。如果你的頁面上有文章迴圈、麵包屑、作者資訊,它還可能再補一組 Article。

問題出在「附加」這兩個字。Rank Math 不負責偵測其他來源是否已經輸出了相同類型的 Schema,所以當你的頁面編輯器(例如 Elementor 的某些 Widget)或主題內建的 schema.org 標記已經宣告過一次,頁面上就會出現兩組同類型結構化資料。Google 檢索到這種情況,會在 Search Console 回報「重複的結構化資料」或「缺少 isPartOf 屬性」等警告。

一行 filter 解決重複 Schema 的核心做法

WordPress 的 Hook 機制給了我們一個乾淨的介入點:Rank Math 提供了 rank_math/json_ld 這個 filter,讓我們在 JSON-LD 陣列被印出之前,直接對資料結構進行修剪。核心邏輯很簡單:只保留第一組 Article、第一組 WebPage、第一組 Organization,其餘全部移除。

把下面這段程式碼放進佈景主題的 functions.php 或是你的 Code Snippets 外掛裡,就能立即生效:

add_filter('rank_math/json_ld', function($data) {
    // 限定只在 Page 類型作用(不要動到文章、產品、自訂 post type)
    if (! is_page()) return $data;
    $first_article_kept = false;
    $first_webpage_kept = false;
    $first_org_kept = false;
    foreach ($data as $key => $item) {
        // 移除 Rank Math 多餘屬性
        unset($data[$key]['isPrimary']);
        if (!isset($item['@type'])) continue;
        $types = (array) $item['@type'];
        if (in_array('Article', $types, true)) {
            if ($first_article_kept) { unset($data[$key]); continue; }
            $first_article_kept = true;
        }
        if (in_array('WebPage', $types, true)) {
            if ($first_webpage_kept) { unset($data[$key]); continue; }
            $first_webpage_kept = true;
        }
        if (in_array('Organization', $types, true)) {
            if ($first_org_kept) { unset($data[$key]); continue; }
            $first_org_kept = true;
        }
    }
    return array_values($data);
}, 99);

這段 filter 掛在優先權 99,確保它是最後一個動作者。進入迴圈後會逐一檢查每個 Schema 節點的 @type 屬性,遇到第一個 Article 就保留,第二個以後全部 unset;WebPage 與 Organization 也採用完全相同的邏輯。迴圈結束前還補了一行 array_values($data),這是為了把陣列索引重新整理成連續數字,避免 JSON 編碼時產出物件而非陣列,導致結構化資料測試工具顯示格式異常。

只針對 Page 修剪的設計考量

你可能會想:為什麼不直接把這段 filter 套用到全站?原因有兩個。

  • • 文章頁面的 Article Schema 通常只有一組:Rank Math 在單篇文章的 JSON-LD 輸出相對節制,重複的情況多半發生在靜態頁面或首頁,因為這些頁面可能同時載入多個迴圈或小工具,導致 Schema 被疊加。
  • • 自訂 post type 可能有自己的 Schema 需求:像是 WooCommerce 的產品頁、LearnDash 的課程頁,Rank Math 會為它們產出專屬的 Product 或 Course Schema,這些類型不在我們的修剪範圍內,沒有重複問題,就不必冒險干預。

如果你真的在全站範圍內都看到重複的 Article 或 WebPage 警告,可以把 if (! is_page()) return $data; 這行移除,讓 filter 對所有內容類型生效。但移除前請務必先在 staging 環境用 Google 複合式搜尋結果測試 驗證一遍,確認文章與自訂內容類型的 Schema 沒有被誤刪。

💡 雙重優勢:修復 Search Console 警告 + 保持 SEO 完整性

  • • 移除重複 Schema 的同時,保留了第一組完整的 Article 與 WebPage 標記,Google 仍然可以正確讀取頁面標題、描述、發佈日期等關鍵資訊。
  • • 多餘的屬性(例如 isPrimary)也一併清除,讓輸出的 JSON-LD 更乾淨,減少結構化資料測試工具發出的非必要警告。

踩坑提醒與實務開發建議

一、不要直接掛在全站而不測試

很多開發者直覺認為「全部清掉最乾淨」,但 Rank Math 為不同 post type 產出的 Schema 結構不完全相同。在沒有備份或 staging 的狀況下直接全站套用,可能導致產品頁的 Offer 或課程頁的 Course Schema 消失,反而影響複合式搜尋結果的顯示。

二、驗證時務必使用 Google 官方工具

程式碼部署後,不要只靠 Search Console 慢慢等爬蟲回報。直接打開 Google 複合式搜尋結果測試,輸入幾個關鍵頁面的網址,確認紅字警告是否消失、Schema 節點數量是否合理。如果測試工具顯示「無效的結構化資料」,通常代表陣列索引沒有用 array_values() 重整,導致 JSON-LD 被解析為物件而非陣列。

三、Rank Math 更新後要回頭檢查

外掛更新可能改變 JSON-LD 陣列的結構,例如新增 @type 的命名方式或是陣列巢狀層級。建議每次 Rank Math 重大版本更新後,花五分鐘用測試工具掃一遍重點頁面,確保 filter 邏輯依然有效。如果你使用 Code Snippets 管理這段程式碼,記得在註解裡標註最後一次驗證通過的 Rank Math 版本號。

四、主題內建 Schema 的取捨

如果重複警告的來源不只是 Rank Math,還有主題內建的結構化資料,單純靠這段 filter 無法完全解決——因為它只攔截 Rank Math 的輸出。這時有兩條路:要嘛停用主題的 Schema 功能(如果開發商有提供選項),要嘛反過來把 Rank Math 的 Schema 模組整個關閉,只靠主題輸出。根據經驗,Rank Math 的 Schema 維護頻率通常比主題高,我會優先保留 Rank Math 的輸出,再把主題端的重複標記移除。

部署完這段 filter,回到 Search Console 點選「驗證修正」,通常幾天內警告數量就會開始下降。結構化資料的世界裡,多不等於好——乾淨、準確、不重複,才是讓 Google 正確理解頁面的關鍵。

文章分享