Breadcrumbs, chips & flavour taxonomy¶
Three small pieces that, together, give Google many more crawl paths and let shoppers cross-navigate the catalogue by family or flavour profile.
When visible breadcrumbs render¶
A visible HTML breadcrumb trail sits directly under <Header /> on every deep
page. The component is markup-only — it never emits JSON-LD. Structured
BreadcrumbList schema stays owned by each page via breadcrumbSchema() in
lib/seo/structured-data.ts, so search engines see exactly one schema entry.
| Page | Trail |
|---|---|
/p/{slug} |
Home › Shop › {Category} › {Product} |
/fruit/{slug} |
Home › The Fruit Atlas › {Family} › {Fruit} |
/atlas/{slug} |
Home › The Fruit Atlas › {Family} (replaces the old thin nav) |
/calendar |
Home › Seasonal calendar |
/b/{slug} |
Home › Saved boxes › |
/t/{tag} |
Home › The Fruit Atlas › {Tag} fruits |
The component — apps/web/components/seo/Breadcrumbs.tsx — renders a
<nav aria-label="Breadcrumb"> with an <ol>, uses lucide ChevronRight at
strokeWidth={1.75} for separators, and marks the last item with
aria-current="page". It uses typedRoutes (as Route) so the tsc build
catches any broken link.
How chips work¶
Family chips and flavour chips render as small clickable pills:
- Family chip (sticker-amber accent) links to
/atlas/{family-key}. - Flavour chips (magenta primary accent) link to
/t/{flavour-slug}, where the slug is the flavour name lowercased and hyphenated.
Chips are live on:
- the PDP (
/p/{slug}) — replaces the previous plain pill strip - the preparation guide (
/fruit/{slug}) — sits above the blurb - the Atlas
FruitCard(/atlas/{slug}) — uses a stacked-link pattern so the flavour chips are real anchors and the card still click-throughs to the PDP / prep guide
Never nest an <a> inside another <a>. The pattern we use: make the card
container relative, give the title a <Link> with an absolute-inset
overlay span, and float the chip row at z-10 so its links stay clickable.
/t/{tag} — flavour landing pages¶
Every flavour that shows up in at least one fruit's flavor[] array gets a
statically-generated landing page:
generateStaticParams()readstagSlugs()fromlib/tags.tsgenerateMetadata()emits title "{Tag} fruits — Fruit Plug", description "Every fruit we source with a {tag} flavour profile.", canonical URL, and full OG/Twitter cards- JSON-LD:
BreadcrumbList+CollectionPage+ItemList - Hero: kicker "FLAVOUR PROFILE",
h1infp-display, one-line sub - Grid: every fruit whose
flavor[]contains this tag, using the local watermarked PNG when available; links to the PDP when Woo has a product, else the prep guide (viafruitHref(slug, wooSlugs)) - "Related flavours" tail: the 4 tags that most frequently co-occur with
this one, computed on the fly by
relatedTags(tag, 4) - Every
/t/{tag}URL is registered inapps/web/app/sitemap.tsat priority 0.6
Helpers — lib/tags.ts¶
tagSlug(tag) // "Sweet" → "sweet"
tagLabel(tag) // "sweet" → "Sweet"
tagSlugs() // all distinct slugs used in FRUITS
fruitsByTag(tag) // FruitRecord[] — stable FRUITS order
relatedTags(tag, n) // top-N co-occurring tags (excluding tag itself)
The file stays under 40 lines on purpose — everything bigger belongs in the taxonomy itself.