If I could add one component to every content site and store I audit, it wouldn't be a fancy schema plugin or a new sitemap setup. It would be a proper breadcrumb trail.
That sounds like a small thing to have an opinion about. But breadcrumbs quietly solve three problems at once: users getting lost, crawlers not understanding site structure, and deep pages with too few internal links. And most implementations I see get at least one part wrong.
What breadcrumbs actually do
A breadcrumb trail is the little line near the top of a page:
Home › Kitchen › Knives › Chef's knives › 8" Carbon Steel Chef's Knife For users, it answers "where am I?" and gives one-click access to broader sections. Someone who lands on a product from search and wants to see similar items doesn't have to hunt through the menu.
For crawlers, it's a consistent set of links from every deep page up to its parent categories. That reinforces your hierarchy and spreads internal links to category pages naturally.
For search results, structured breadcrumb data can let Google show a readable path instead of a raw URL in some results.
Three wins, one component. That's why I think it deserves more attention than it gets.
The common ways teams get it wrong
1. They're not real links
I regularly see breadcrumbs built as <span> elements with click handlers:
// Don't do this <span onClick={() => router.push("/kitchen")}>Kitchen</span> Users with a mouse might not notice. Crawlers can't follow it. Keyboard and screen reader users get nothing useful. A breadcrumb that isn't a link is decoration.
2. They show the path the user took, not the page's place
"History" breadcrumbs (based on how you navigated) change from visit to visit. They're confusing for users and give crawlers inconsistent signals. A breadcrumb should describe where the page lives in the site, the same for everyone.
3. They don't match the URL or the structured data
The visible trail says "Home › Knives", the JSON-LD says "Home › Kitchen › Knives", and the URL is /products/12345. Pick one source of truth and generate both the visible trail and the structured data from it.
4. They're missing on the pages that need them most
Teams add breadcrumbs to category pages and forget product pages, articles, or help docs. Those deep pages are exactly where breadcrumbs help most.
5. The last item is a link to itself
The current page should be the last item, marked as current, not a link back to itself.
What a good one looks like
Here's a React component that does the basics right: real links, accessible markup, and structured data generated from the same array.
type Crumb = { name: string; href: string }; export function Breadcrumbs({ trail, baseUrl }: { trail: Crumb[]; baseUrl: string }) { const jsonLd = { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: trail.map((c, i) => ({ "@type": "ListItem", position: i + 1, name: c.name, item: new URL(c.href, baseUrl).toString(), })), }; return ( <nav aria-label="Breadcrumb"> <ol className="breadcrumbs"> {trail.map((c, i) => { const isLast = i === trail.length - 1; return ( <li key={c.href}> {isLast ? ( <span aria-current="page">{c.name}</span> ) : ( <a href={c.href}>{c.name}</a> )} </li> ); })} </ol> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> </nav> ); } A few details worth noting:
-
<nav aria-label="Breadcrumb">and an ordered list give screen readers the right context. -
aria-current="page"on the last item tells assistive technology which one is the current page. - Real
<a href>elements work for everyone, with or without JavaScript. - One array produces both the visible trail and the structured data, so they can't drift apart.
The separators (›, /) belong in CSS, not the markup, so screen readers don't read them aloud:
.breadcrumbs { display: flex; flex-wrap: wrap; list-style: none; padding: 0; } .breadcrumbs li + li::before { content: "›"; padding: 0 0.5em; color: #888; } Where the trail should come from
The hardest part isn't the component. It's deciding each page's place in the hierarchy.
- For stores, a product's primary category. If a product is in several categories, pick one as canonical for the breadcrumb.
- For blogs and docs, the section and sub-section.
- For help centres, category and topic.
Store it as data (a primaryCategoryId on the product, a section on the article) rather than inferring it from the URL or the user's navigation. Then every page gets a consistent trail.
"But our site is flat"
If your site really is flat (a homepage and a dozen pages), breadcrumbs add little. Skip them. But most sites that call themselves flat actually have implicit sections: blog posts, products, docs. Making that structure visible usually helps users and crawlers both.
How to check yours
Open a few deep pages and ask:
- Are breadcrumbs present?
- Are they real links? (Right-click: can you "open in new tab"?)
- Is the trail the same however you arrived?
- Does the structured data match the visible trail?
- Is the last item the current page, not a link?
A site audit tool like seotto.com can flag pages with missing or mismatched breadcrumb data at scale, but those five questions on ten pages will tell you most of what you need to know.
My actual opinion, stated plainly
Breadcrumbs are cheap, boring and effective. Treat them as a core component of your design system, not an optional extra, and build them properly once. Few changes help users, accessibility and SEO at the same time this cheaply.
Related reading
- Your structured data broke 3 weeks ago and nobody noticed: monitoring JSON-LD with a script
- How to read your server logs like Googlebot's diary (with a short Python script)
- Image SEO: 7 things I'd do differently on the next big site (starting with hashed filenames)
- Out of stock, discontinued, or gone forever? An SEO checklist for product pages developers actually own
Does your site have breadcrumbs? If not, what's the reason: design, a flat structure, or nobody ever asked for them? Tell me in the comments.


