Breadcrumb JSON-LD 与可见面包屑对不上

Rich Results Test 过了,但 Search Console 报 Breadcrumbs,或 SERP 里的面包屑看起来不对。先分清真正的原因,再让 JSON-LD 与可见 UI 对齐(2026 年 6 月)。

页面显示 Home → AI Tools → ChatGPT issuesRich Results TestBreadcrumbList 有效。但 Search Console 的 Breadcrumbs 报告报警告或错误,或者 SERP 里的面包屑看起来不对、干脆没了。动手改之前,先把问题分成两种完全不同的情况,因为它们的处理方向正好相反:

  • SERP 里的面包屑短了 / 没了,但 Search Console 是干净的。 多半你这边没毛病。从 2025 年 1 月起,Google 在手机搜索结果里不再显示面包屑(只显示域名),桌面端仍然显示。见下文手机端这一节
  • Search Console 的 Breadcrumbs 报告里有 Error 或 Warning 项。 这是你这边的标记问题,确实会影响展示资格。直接跳到修标记

底层的经典原因是:JSON-LD 与可见 UI 是各自独立生成的——数据源不同、标签规则不同、locale 处理不同。Google 真正的规则(见结构化数据政策)并不是”跟 UI 一字不差”,而是不要标记页面上读者看不到的内容不要误导。所以目标是对齐,不是逐像素吻合:BreadcrumbList 里的名称和链接必须反映页面上真实存在的路径。

你属于哪一类

现象可能原因去哪看
手机上面包屑没了、桌面正常2025 年 1 月的手机端改动,不是你的标记问题手机端
Search Console:Either "name" or "item.name" should be specified某个列表项缺 name具名报错
Search Console:Missing field "item" (in "itemListElement")非叶子项没有 item URL具名报错
Search Console:Missing field "id" (in "itemListElement.item")有两项 URL 相同具名报错
UI 写 “AI Tools”、JSON-LD 写 ai-tools该用展示名的地方用了 slug原因 1
本地化页面的 schema 里是英文标签JSON-LD 没翻译原因 4
JSON-LD 比页面多几层为”SEO”加的幻影层原因 5

SERP 面包屑没了或变短了,但 Search Console 是干净的

这是 2026 年 6 月最常见的虚惊一场。2025 年 1 月 Google 宣布,在所有语言和地区的手机搜索摘要里不再显示面包屑,只显示域名。桌面摘要不变,仍然显示完整路径。这一改动现已写进官方面包屑文档,其功能可用性那行写的是 “available on desktop in all regions and languages where Google Search is available”——并未列出手机端。

所以如果你用手机查,看到的只是 aitoolsguidebook.com,而以前是 aitoolsguidebook.com › AI Tools › ChatGPT issues,这是预期行为,不是标记 bug。无需修改、无需任何操作——Google 仍然会读取并使用这份标记,桌面 SERP 和 Breadcrumbs 报告都不受影响。换到桌面端搜同一个词确认即可。

Search Console 的 Breadcrumbs 报告也可能延迟几天。如果你的源码标记是对的,先相信源码,让报告慢慢追上。

具名的 Search Console 报错及含义

下面这些消息才是真正让页面失去面包屑富媒体资格的,每条都对应一个具体修法。

  • Either "name" or "item.name" should be specified (in "itemListElement")——某个 ListItem 没有可读名称。给每一项都加上 name 字符串(如果用嵌套对象写法,就给 item.name)。
  • Missing field "item" (in "itemListElement")——非最后一项缺目标 URL。除最后的叶子项外,每一项都需要 item(绝对 URL)。叶子项可以省略 item;按 Google 文档,省略时 “Google uses the URL of the containing page”(用所在页面的 URL)。如果这个错误报在你以为的叶子项上,多半是你的 position 编号有误,Google 因此认为是某个中间项缺了 item
  • Missing field "id" (in "itemListElement.item")——通常是因为有两个列表项指向同一个 URL,Google 把 URL 当作 item 的 id,于是看到重复。把各非叶子项的 URL 改成唯一的,或给嵌套的 item 对象一个独立的 @id

可以一眼核对的硬性要求(依据 Google 的面包屑文档,2026 年 6 月):至少两个 ListItem;每一项都要有 name(展示给用户看的标题);position 是整数,position 1 表示起点、逐项加 1;item 是该面包屑目标的 URL(用绝对地址);整条路径要反映用户到达该页的典型路径(不要捏造一个不存在的层级)。

常见的对齐类原因

按命中率从高到低。

1. JSON-LD 用 slug 但 UI 用展示名

{ "name": "ai-tools", "item": "https://site.com/category/ai-tools/" }

可见 UI 显示 “AI Tools”(大写、带空格),JSON-LD 的 name 写的是 ai-tools(原始 slug)。这是典型的”标记了页面上看不到的内容”违规。

怎么判断:把可见标签和 JSON-LD name 一个字一个字地对。大小写算、空格也算。

2. URL 大小写或尾斜杠差异

JSON-LD:https://site.com/Category/AI-Tools。可见锚点:https://site.com/category/ai-tools/。逻辑目标相同、字符串不同——而且如果 canonical 版本和路径里那个不一致,还可能造成软重复。

怎么判断:逐条把锚点的 href 跟对应的 JSON-LD item 比。两者应该解析到同一个 canonical URL(大小写一致、尾斜杠策略一致)。

3. UI 省略首页、JSON-LD 包含(或反过来)

UI 显示 “AI Tools → ChatGPT issues”(无 Home),JSON-LD 位置 1 是 Home,或者反过来。如果是有意且前后一致的选择,没问题;但意外的两层不一致,通常说明两个生成器对不上。

怎么判断:数可见面包屑项数 vs. itemListElement.length。若无意中不相等,就是两条代码路径在飘。

4. 本地化页面发的是英文 JSON-LD

非英文页面 UI 显示翻译后的标签(“首页 → AI 工具”),但 JSON-LD 仍写英文原文(“Home → AI Tools”)。这样 schema 描述的就是页面上没有的内容。

怎么判断:打开一个非默认语言的 URL,对比 JSON-LD name 字段和可见标签。

5. 为”SEO”加的幻影层

有人加了只存在于标记里的中间层(Home → AI → Tools → AI Tools → ChatGPT),以为能加分。页面实际显示的层更少。这正是 Google 政策禁止的”误导 / 不可见”情形,会压制富媒体结果。

怎么判断:数 JSON-LD 的位置数。如果超过可见路径(加上叶子)的数量,就是有幻影层,删掉。

一个合法的例外:当确实有多条真实导航路径通向同一页面时,Google 明确允许一个页面上有多个 BreadcrumbList 对象(数组形式)。这跟往一条路径里塞假层不同——每个列表都必须描述一条真实路径。

最快的修法

最稳的修法是:把面包屑只算一次,让可见 UI 和 JSON-LD 都从这一个数组渲染。这样两者就不可能再飘。

第 1 步:对单页面把两层 diff 出来

# 可见面包屑的锚文本和 href
curl -s "https://site.com/article/" | grep -oP '<nav[^>]*aria-label="breadcrumb[^>]*>[\s\S]+?</nav>'

# JSON-LD 面包屑块
curl -s "https://site.com/article/" | grep -oP '"@type":\s*"BreadcrumbList[\s\S]+?</script>'

逐位置对比。哪一项的 name 或 canonical URL 不一样,bug 就在那。

第 2 步:两层从同一个源生成

---
const breadcrumb = [
  { name: 'Home', url: '/' },
  { name: 'AI Tools', url: '/category/ai-tools/' },
  { name: article.title, url: Astro.url.pathname },
];

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": breadcrumb.map((b, i) => ({
    "@type": "ListItem",
    "position": i + 1,
    "name": b.name,
    // 按 Google 规范,叶子项省略 `item`,让最后一位没有链接
    ...(i < breadcrumb.length - 1
      ? { "item": new URL(b.url, Astro.site).toString() }
      : {}),
  })),
};
---
<nav aria-label="breadcrumb">
  <ol>
    {breadcrumb.map((b, i) => (
      <li>
        {i < breadcrumb.length - 1
          ? <a href={b.url}>{b.name}</a>
          : <span>{b.name}</span>}
      </li>
    ))}
  </ol>
</nav>
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>

一个 breadcrumb 数组驱动两种表示,于是 name 一定一致,叶子项也正确地不带 item

第 3 步:在源头本地化

const breadcrumb = [
  { name: t('breadcrumb.home'), url: lang === 'en' ? '/' : `/${lang}/` },
  { name: t(`category.${article.category}`), url: `/${lang}/category/${article.category}/` },
  { name: article.title, url: Astro.url.pathname },
];

因为两层都读同一个 breadcrumb 数组,标签翻译一次,UI 和 JSON-LD 一起改好。

第 4 步:加 CI 守卫(子集检查,不是逐字相等)

现在用严格的逐字相等断言太脆——它会误杀”可见路径精简、schema 路径更全”这种合法模式,也会误杀多个 BreadcrumbList 的情况。改成断言每个 JSON-LD name 都出现在可见面包屑里,这样能抓出 slug、未翻译标签、幻影层,又不会误报:

// scripts/check-breadcrumb.mjs
import fs from 'node:fs';
import { parse } from 'node-html-parser';

for (const file of getDistHtmlFiles()) {
  const root = parse(fs.readFileSync(file, 'utf8'));
  const visible = new Set(
    root.querySelectorAll('nav[aria-label="breadcrumb"] a, nav[aria-label="breadcrumb"] span')
      .map(el => el.text.trim())
  );

  for (const script of root.querySelectorAll('script[type="application/ld+json"]')) {
    let data;
    try { data = JSON.parse(script.text); } catch { continue; }
    for (const node of [].concat(data)) {
      if (node['@type'] !== 'BreadcrumbList') continue;
      for (const item of node.itemListElement) {
        const name = item.name ?? item.item?.name;
        if (name && !visible.has(name)) {
          console.error(`MISMATCH in ${file}: JSON-LD "${name}" not in visible breadcrumb`);
        }
      }
    }
  }
}

第 5 步:校验并重新提交

把修好的 URL 放进 Rich Results Test——它只显示 Google 能展示的功能,所以判断面包屑资格用它最对(要做完整 schema.org 规范检查再用 Schema Markup Validator)。然后在 Search Console 对代表性页面跑 URL Inspection → Test live URL,再点 Request indexing。Breadcrumbs 报告通常 1–2 周刷新一次;有效项会在下次重新抓取后退出 Error/Warning 状态。

怎么确认修好了

  1. Rich Results Test 报告该页符合 Breadcrumbs 资格、零错误,且预览里的名称跟你的可见路径一致。
  2. 每个 ListItemname 都能在页面可见面包屑里找到。
  3. URL Inspection 的渲染 HTML 里显示的 BreadcrumbList 跟你发布的一致(排除 CDN 或边缘转换把它剥掉的情况)。
  4. 下次重新抓取后,Search Console Breadcrumbs 报告里该 URL 进入 Valid,无 Error/Warning 项。

预防建议

  • 可见面包屑和 JSON-LD 用同一个 helper 生成。
  • 双语站两层都读同一翻译源。
  • item URL 用绝对地址且 canonical(大小写、尾斜杠策略一致)。
  • 不要为 SEO 往单条路径里塞幻影层;若确实需要多条真实路径,改成输出多个 BreadcrumbList 对象。
  • 改 URL 结构时同时重新生成两层——绝不手动 patch JSON-LD。

FAQ

  • 手机搜索结果里的面包屑不见了,是我搞坏了吗? 不是。从 2025 年 1 月起 Google 在手机摘要里隐藏面包屑、只显示域名。桌面端仍显示完整路径,标记照样被读取。无需修改。
  • 可以省略首页吗? 可以,是允许的。只要 UI 和 JSON-LD 保持一致即可;真正的问题是两层之间意外不一致。
  • 最后一项要链到当前页吗? 按 Google 规范,最后的叶子项可以省略 item;省略时 Google 会用所在页面的 URL 作为这一位置。给叶子项加上 item 也接受。两种写法都行——全站统一一种约定即可。
  • Breadcrumbs 报告显示没有展示量、像是空的,是坏了吗? 多半不是。自 2025 年 1 月的手机端改动后,面包屑只在桌面端渲染,所以和可见功能挂钩的展示类指标自然下来了。该报告仍然在跟踪标记是否有效(Valid / Warning / Error),这才是你该盯的;Google 照样会读取并使用这份标记。
  • Rich Results Test 过了,但 Search Console 还报警告——以谁为准? 两者都可能对。测试反映当前源码;报告反映上次抓取,可能延迟几天。修源码、跑 live 测试、请求收录,让报告追上即可。
  • 我的可见面包屑比 schema 路径更简,这算违规吗? 不一定。Google 禁止的是标记页面上看不到的内容;但”可见路径更短、背后有一条更全且真实的 schema 路径”是允许的。别捏造不存在的导航层级就行。

相关阅读

标签: #SEO #排查 #排查 #结构化数据 #面包屑 #JSON-LD