Breadcrumb JSON-LD Doesn't Match Your Visible Breadcrumb

Rich Results Test passes but Search Console flags Breadcrumbs, or the SERP trail looks wrong. Diagnose the real cause and align JSON-LD with the visible UI (June 2026).

Your page shows Home → AI Tools → ChatGPT issues. The Rich Results Test says the BreadcrumbList is valid. But Search Console’s Breadcrumbs report shows warnings or errors, or the trail in the SERP looks wrong or missing. Before you rewrite anything, split the problem into two very different cases, because the fix is opposite for each:

  • The SERP trail looks wrong/short, but Search Console is clean. Most likely nothing is broken on your side. Since January 2025 Google no longer renders breadcrumbs on mobile search results (you see only the domain); they still render on desktop. See the mobile case below.
  • Search Console’s Breadcrumbs report shows Error or Warning items. That is a markup problem on your side and it does affect eligibility. Jump to fixing the markup.

The classic underlying cause is that your JSON-LD is generated independently from the visible UI: different data sources, different label conventions, different locale handling. Google’s actual rule (from the structured data policies) is not “byte-identical to the UI” — it is don’t mark up content that is not visible to readers, and don’t mislead. So the goal is alignment, not pixel-matching: the names and links in your BreadcrumbList must reflect a real, on-page path.

Which bucket are you in

SymptomLikely causeWhere to go
SERP trail gone on phone, fine on desktopJan 2025 mobile change, not your markupMobile case
Search Console: Either "name" or "item.name" should be specifiedMissing name on a list itemNamed errors
Search Console: Missing field "item" (in "itemListElement")Non-leaf item has no item URLNamed errors
Search Console: Missing field "id" (in "itemListElement.item")Two items share the same URLNamed errors
UI says “AI Tools”, JSON-LD says ai-toolsSlug used where display name belongsCause 1
Localized page shows English labels in schemaJSON-LD not translatedCause 4
JSON-LD has more levels than the pagePhantom “SEO” levelsCause 5

The SERP breadcrumb is missing or short but Search Console is clean

This is the most common false alarm as of June 2026. In January 2025 Google announced it would stop showing breadcrumbs in mobile search snippets in all languages and regions, displaying only the domain. Desktop snippets are unchanged and still show the full trail. The change is now baked into the official breadcrumb docs, whose feature-availability line reads “available on desktop in all regions and languages where Google Search is available” — mobile is not listed.

So if you check on your phone and see only aitoolsguidebook.com where you used to see aitoolsguidebook.com › AI Tools › ChatGPT issues, that is expected behavior, not a markup bug. There is nothing to fix and no action required — Google still reads and uses the markup, and the desktop SERP and the Breadcrumbs report are unaffected. Confirm by viewing the same query on desktop.

Search Console’s Breadcrumbs report can also lag by a few days. If your source markup is correct, trust the source first and let the report catch up.

Named Search Console errors and what they mean

These are the messages that actually make a page ineligible for the breadcrumb rich result. Each maps to a concrete fix.

  • Either "name" or "item.name" should be specified (in "itemListElement") — a ListItem has no readable name. Give every item a name string (or item.name if you use the nested object form).
  • Missing field "item" (in "itemListElement") — a non-last item has no destination URL. Every item except the final leaf needs an item (an absolute URL). The leaf may omit item; per Google’s docs, when it does, “Google uses the URL of the containing page.” If this error fires on what you think is the leaf, your position numbering is probably wrong, so Google thinks a middle item is the one missing item.
  • Missing field "id" (in "itemListElement.item") — usually fires when two list items resolve to the same URL, because Google treats the URL as the item id and now sees a duplicate. Make each non-leaf URL unique, or give the nested item object a distinct @id.

Requirements you can check at a glance (per Google’s breadcrumb docs, June 2026): at least two ListItem entries; every item needs a name (the title shown to the user); position is an integer where position 1 signifies the beginning and each step increments by 1; item is the URL of the breadcrumb target (use absolute URLs); the trail represents a typical user path to the page (it should not fabricate a hierarchy that does not exist).

Common alignment causes

Ordered by hit rate, highest first.

1. JSON-LD uses slugs while the UI uses display names

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

The visible UI shows “AI Tools” (capitalized, spaced); the JSON-LD name says ai-tools (the raw slug). This is a textbook “marked-up content not visible on the page” violation.

How to spot it: compare the visible label to the JSON-LD name, character for character. Capitalization and spacing both count.

2. URL casing or trailing-slash differences

JSON-LD: https://site.com/Category/AI-Tools. Visible anchor: https://site.com/category/ai-tools/. Same logical destination, different strings — and if the canonical version differs from the one in the trail, you can also create a soft duplicate.

How to spot it: compare each anchor href against the matching JSON-LD item. They should resolve to the same canonical URL (same casing, same trailing-slash policy).

3. UI omits the homepage; JSON-LD includes it (or vice versa)

UI shows “AI Tools → ChatGPT issues” (no Home); JSON-LD has Home at position 1. Or the reverse. A deliberate, consistent choice is fine, but an accidental difference between layers usually means the two generators disagree.

How to spot it: count visible breadcrumb items vs. itemListElement.length. If they differ for no intentional reason, you have drift between the two code paths.

4. Localized page ships English JSON-LD

A non-English page shows translated labels in the UI (“首页 → AI 工具”) but the JSON-LD still emits the English originals (“Home → AI Tools”). The schema then describes content that is not on the page.

How to spot it: open a non-default-language URL and compare JSON-LD name fields to the visible labels.

5. Phantom levels added for “SEO”

Someone inserted intermediate levels that exist only in the markup (Home → AI → Tools → AI Tools → ChatGPT) believing it boosts SEO. The page shows fewer levels. This is exactly the “mislead / not visible” case Google’s policy prohibits, and it can suppress the rich result.

How to spot it: count JSON-LD positions. If the count exceeds the visible trail (plus the leaf), you have phantom levels. Remove them.

One legitimate exception: Google explicitly allows multiple BreadcrumbList objects on a page (an array) when several real navigation paths lead to it. That is different from padding one trail with fake levels — each list must still describe a genuine path.

The fastest fix

The durable fix is to compute the breadcrumb once and render both the visible UI and the JSON-LD from that single array. Then they cannot drift.

Step 1: Diff the two layers for one page

# Visible breadcrumb anchor text and hrefs
curl -s "https://site.com/article/" | grep -oP '<nav[^>]*aria-label="breadcrumb[^>]*>[\s\S]+?</nav>'

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

Compare them position by position. Where the name or the canonical URL differs, you have your bug.

Step 2: Generate both from one source

---
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,
    // Omit `item` on the leaf so the last position has no link, per Google's spec
    ...(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>

One breadcrumb array drives both representations, so the name values are guaranteed identical and the leaf correctly drops its item.

Step 3: Localize at the source

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 },
];

Because both layers read the same breadcrumb array, translating the labels once fixes the UI and the JSON-LD together.

Step 4: Add a CI guard (subset check, not byte-equality)

A strict byte-equality assertion is too brittle now — it breaks the legitimate “simplified visible trail, fuller schema path” pattern and the multiple-BreadcrumbList case. Assert instead that every JSON-LD name actually appears in the visible breadcrumb, which catches slugs, untranslated labels, and phantom levels without false positives:

// 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`);
        }
      }
    }
  }
}

Step 5: Validate and resubmit

Open the fixed URL in the Rich Results Test — it shows only Google-eligible features, so it is the right tool for breadcrumb eligibility (use the Schema Markup Validator only for full schema.org spec checks). Then in Search Console run URL Inspection → Test live URL on a representative page and Request indexing. The Breadcrumbs report typically refreshes over 1–2 weeks; valid items move out of the Error/Warning state on the next recrawl.

How to confirm it’s fixed

  1. Rich Results Test reports the page eligible for Breadcrumbs with zero errors, and the previewed names match your visible trail.
  2. The name of every ListItem is also visible somewhere in the on-page breadcrumb.
  3. URL Inspection’s rendered HTML shows the same BreadcrumbList you ship (rules out a CDN or edge transform stripping it).
  4. After the next recrawl, the Search Console Breadcrumbs report shows the URL under Valid with no Error/Warning items.

Prevention

  • Generate the visible breadcrumb and the JSON-LD from one helper.
  • On bilingual sites, both layers must read the same translation source.
  • Keep item URLs absolute and canonical (consistent casing and trailing-slash policy).
  • Don’t pad a single trail with phantom levels for SEO; if you need more than one real path, ship multiple BreadcrumbList objects instead.
  • When you change URL structure, regenerate both layers — never hand-patch the JSON-LD.

FAQ

  • The breadcrumb vanished from my phone’s search result. Did I break something? No. Since January 2025 Google hides breadcrumbs on mobile snippets and shows only the domain. Desktop still shows the full trail, and the markup is still read. Nothing to fix.
  • Can I omit the homepage from the breadcrumb? Yes, it’s allowed. Just be consistent across the UI and the JSON-LD; an accidental mismatch between layers is the actual problem.
  • Should the last item link to the current page? Per Google’s spec the final leaf may omit item; when it does, Google uses the URL of the containing page for that position. Including an item on the leaf is also accepted. Either form works — just apply one convention site-wide.
  • The Breadcrumbs report shows no impressions or seems empty. Is it broken? Often not. Since the Jan 2025 mobile change, breadcrumbs only render on desktop, so impression-style metrics tied to the visible feature dropped. The report still tracks markup validity (Valid / Warning / Error), which is what you should watch; Google continues to read and use the markup.
  • Rich Results Test passes but Search Console still warns — who’s right? Both can be. The test reflects current source; the report reflects the last crawl and can lag days. Fix the source, retest live, request indexing, and let the report catch up.
  • My visible breadcrumb is simpler than my schema path. Is that a violation? Not necessarily. Google forbids marking up content that is not visible, but a shorter visible trail backed by a fuller, truthful schema path is allowed. Don’t invent levels that aren’t real navigation.

Tags: #SEO #Troubleshooting #Debug #Structured data #Breadcrumb #JSON-LD