Preparation guides — /fruit/[slug]¶
Public, SEO-friendly content pages for every fruit in the taxonomy. They sit
alongside the Woo PDP (/p/[slug]) and the Atlas family page
(/atlas/[slug]) — same data, different intent. The PDP sells a SKU; the
prep guide ranks for "how to eat X" queries and converts curiosity into a
basket add.
Routes¶
| Route | Purpose |
|---|---|
/fruit/[slug] |
Preparation guide per fruit. Static-rendered from the taxonomy. |
/atlas |
Botanical family index. |
/atlas/[slug] |
Family page. Each fruit card now routes via fruit-routes.ts. |
Every fruit in apps/web/lib/fruit-taxonomy.ts gets a guide automatically —
generateStaticParams() derives the list from FRUITS.
Routing helper¶
apps/web/lib/fruit-routes.ts exports two functions used wherever we link to
a fruit from another surface:
wooSlugSet(slugs: Iterable<string>): Set<string>
fruitHref(slug: string, wooSlugs: Set<string>): Route
hasPdp(slug: string, wooSlugs: Set<string>): boolean
The decision is binary: if a Woo product with that slug exists, we link to
the PDP; otherwise we fall back to the prep guide. The Atlas family page
fetches the full Woo product list once per request, builds the Set, and
passes it to every FruitCard. The prep guide does the same to label its
"Buy this fruit" CTA — /p/{slug} when a product exists, otherwise
/shop?search={name}.
Page anatomy¶
Top to bottom:
- Cinematic hero — local 2048×2048 PNG from
/public/media/fruits/, resolved vialib/fruit-images.ts. Radial vignette + bottom gradient, centered title + scientific name + family link. - Availability strip — green when in season, amber on the shoulder,
neutral when out of season or unrecognised. Parsed from the
season_ukfield with a tiny month parser (see below). - Long-form content — the 45–60 word
blurbas a lede, then the ~150-worddescriptionfrom the taxonomy, sectioned by paragraph. Verbatim — we never re-author the per-fruit copy. - Did you know? — three Wikipedia-sourced facts per fruit
(origin / cultivars & relatives / quirky detail), rendered as bullet
points with a Wikipedia source link. Data lives in
apps/web/lib/fruit-wiki-facts.ts(a sibling of the taxonomy file — the taxonomy itself stays untouched). The raw fetch output is cached atreports/wiki-fact-cache-2026-04-25.jsonso re-runs don't re-hit Wikipedia. Missing slugs skip the section silently. - How to eat — generic, four-step prep block (ripeness → wash → cut
→ pair). Deliberately untethered to any specific cultivar so we never
invent facts. The family-level
tipfromFAMILIES[fam].tipcloses the section. - Buy CTA — primary action plus
saleFromCost(cost_gbp)price when the taxonomy carries a cost. - Sidebar — family link, fact dl (origin / season / flavour), similar fruits in the same family, back-to-Atlas.
Heading order¶
Strict h1 → h2 → h3. The h1 is the fruit name in the hero. Each
content block opens with h2; the four prep steps use h3. The
availability strip uses role="status" rather than a heading so it
doesn't perturb the outline. Sidebar sections also use h2 so screen
readers can navigate them.
Season parser¶
Recognises:
"Year-round"/"Year-round (imported)"→year-round(green strip)"Mon – Mon"(en-dash or hyphen) →in-seasonif today is inside the window,shoulderfor the calendar months immediately before/after, otherwiseout-of-season- Anything else (e.g.
"Limited drops") →unknown, neutral strip
Wrap-around windows like "Sep – Feb" are handled.
SEO¶
generateMetadata reads the seo block on the FruitRecord first, falling
back to "How to eat {Name} | Preparation Guide — Fruit Plug" and the
fruit's blurb when the taxonomy hasn't authored one. Canonical is
/fruit/{slug}. The page emits two JSON-LD blocks: BreadcrumbList and
Article (the fruit is the subject; image and inLanguage: en-GB set).
When fruit-wiki-facts.ts has entries for the slug the Article
schema gains additionalProperty PropertyValue rows (one per fact,
labelled Origin / Cultivars & relatives / Notable fact) and a citation
array of Wikipedia article URLs.
Every prep guide is added to app/sitemap.ts at priority 0.7, beside the
existing family routes.
PDP cross-link¶
app/p/[slug]/page.tsx looks the slug up against the taxonomy
(getFruitBySlug) and, when there's a match, renders a "Preparation guide"
card under the long description. The link uses as Route for typedRoutes.
Content rules (no fabrication)¶
The taxonomy's description is the source of truth. The page renders it
verbatim, paragraph-split. The only generated copy is the four-step "How
to eat" block, which is intentionally generic — ripeness cues, wash + chill,
cut technique, simple pairings. Anything cultivar-specific must live in the
taxonomy file, where it's reviewed once.
When a fruit has no description set, only the blurb + the generic prep
block render. We do not invent a description.
Wikipedia enrichment — fruit-wiki-facts.ts¶
Each fact in apps/web/lib/fruit-wiki-facts.ts must be verifiable
against the cited Wikipedia article as of the cache date. Claims that
the source doesn't directly support are flagged partial in
reports/wiki-fact-cache-2026-04-25.json and kept at species-level
rather than invented. When re-running enrichment:
- Read the JSON cache first; only re-fetch slugs whose article changed materially.
- Keep the three-fact order fixed (origin → cultivars → quirky) so
the
additionalPropertyJSON-LD labels line up. - Never duplicate claims already present in the taxonomy's
description— the point of this block is to add verified context, not restate copy. apps/web/lib/fruit-taxonomy.tsis read-only from the enrichment pass — new facts go in the sibling file.