# Site structure proposal
### Duty Free Professor, for Mark's review. 3 September 2026.

> **Superseded.** This proposal now lives as the commentable `/structure` page; its
> content is `main/web/src/lib/structure.ts` and the decisions are recorded in
> `build-plan-2026-09.md` §6. This file is the 3 September snapshot sent to Mark and is
> kept for provenance only; its counts were true on that date and are not updated.

This sets out how brands, categories, airports and products become pages and
web addresses. It is the decision that determines whether the site grows into
several thousand pages worth ranking, or several thousand thin ones. I would
rather agree it with you before building than rebuild it after.

Everything below is measured against the real catalogue at soft-launch scale:
**16 airports, 3,770 products, 1,186 brands.**

---

## 1. What the data will actually support

Page types only earn their place if there is enough behind them. The counts:

| | Number | Comment |
|---|---|---|
| Airports | 16 | Each has 130 to 1,489 products |
| Categories | 10 | Wine 1,117, Whisky 952, Champagne 280, Tequila 221, Gin 213, Cognac 202, Liqueurs 197, Vodka 188, Rum 103, Beer 19 |
| Brands with 20+ products | 21 | 644 products between them |
| Brands with 10 to 19 | 44 | 570 products |
| Brands with 5 to 9 | 144 | 924 products |
| Brands with 3 or 4 | 191 | 641 products |
| Brands with 2 | 205 | 410 products |
| **Brands with only 1** | **581** | **581 products** |

So roughly half of all brands have a single product. A page per brand with no
threshold would create 581 pages carrying one bottle each, which is the
definition of thin. That single fact drives most of what follows.

Two crossings are also worth knowing:
- **633** brand-and-airport pairs have 3 or more products.
- **111** category-and-airport pairs have 8 or more products.

## 2. Proposed URL structure

```
/products/<product-slug>-<id>              Johnnie Walker Blue Label 1L
/brands/<brand-slug>                       All Johnnie Walker, every airport
/categories/<category-slug>                All whisky, every airport
/airports/<iata>-<city-slug>               London Heathrow
/airports/<iata>-<city-slug>/<category>    Whisky at London Heathrow
```

Product URLs already work this way and are indexed as such, so they stay. The
numeric suffix is the identity and the words are for humans; a renamed product
keeps its address and any stale slug redirects permanently to the current one.

**The fifth line is the one I would argue hardest for.** "Whisky at Heathrow"
is what people actually search, it is where the comparison tool is genuinely
useful, and 111 of those pages have real substance behind them. It is the
strongest commercial page type on the site.

## 3. Thresholds, so nothing thin gets built

| Page type | Built when | Expected count |
|---|---|---|
| Product | always | 3,770 |
| Brand | the brand has **3 or more** products | about 400 |
| Category | always | 10 |
| Airport | always | 16 |
| Category at airport | that pairing has **8 or more** products | 111 |

Total indexable: roughly **4,300 pages**, every one of them with a real price
table behind it.

Brands with one or two products get **no page at all**. Their products still
appear, still carry the brand name, and still link to the category and the
airports that stock them. When such a brand crosses the threshold, its page
comes into existence automatically. Nothing is lost, and no empty page is
created in the hope of catching a search.

**Brand and airport pairs get no standalone page**, despite 633 of them
qualifying on volume. The brand page already carries a price-by-airport table,
which answers "Johnnie Walker at Heathrow" without a separate URL, and 633
extra near-duplicate pages is exactly how a site earns a thin-content problem.
I am open to revisiting this once the first pages are indexed and we can see
what the queries actually look like.

## 4. What goes on each page type

Every page carries its content in the HTML itself, so search engines and AI
assistants read it without executing anything, plus schema.org markup and a
last-checked date on every price.

**Product.** Name, brand, size, ABV where it applies, barcode, image, the price
at every airport that stocks it with the date checked, competition medals, and
links to its brand, its category and each airport.

**Brand.** What the brand is, then every product we hold for it with prices
across all airports, grouped by category where the brand spans several, its
medals, and links to each airport where it is stocked.

**Category.** The category explained, its products ranked by something useful
rather than alphabetically, the airports that stock it best, and its notable
brands.

**Airport.** Which shops operate there, what it is known for, the biggest
current savings, travel exclusives, its categories, and editorial about the
airport itself. Adam's writers are producing this content: food, lounges,
terminal tips. The comparison tool is embedded preset to that airport, which
was your suggestion and I think it is the right one.

**Category at airport.** Every product in that category at that airport with
prices, how it compares to the same category elsewhere, and the exclusives.

## 5. Answering Adam's question about brand pages

He asked whether a brand page should reuse the product-page information plus
some extras, or whether he should specify everything he wants and we cherry
pick. He was clear he does not want empty fields, and wants room to add things
later such as tasting notes.

**My proposal: the price data is the spine, and editorial fields are optional
and render only when filled.** So a brand page always has substance, because
the prices are always there. Anything Adam adds later, a description, a
history, tasting notes, an image, appears when it exists and simply is not
rendered when it does not. No empty labels, and no page redesign needed to add
a field in six months.

That way he can send a long wish list without any of it becoming a blocker.

## 6. Handling categories beyond drinks

Cosmetics and fragrance are being added at soft launch, roughly twenty products
each, and they do not fit the spirits shape: no ABV, and size is millilitres of
liquid rather than bottle volume in the same sense. Perfume has concentration
(eau de toilette against eau de parfum), skincare has volume and format.

So attributes are **per category, not global**. A product page renders the
attributes its category defines and nothing else, which is the same rule as the
optional editorial fields above. This keeps a fragrance page from displaying an
empty ABV row, and means adding a category later is configuration rather than a
rebuild.

## 7. Internal linking

- **Mega menu**: categories, the leading brands, and all airports, so every hub
  is one click from anywhere and the crawl path is shallow.
- **Breadcrumbs** on every page, marked up for search engines.
- **Product** links to its brand, its category, and each airport that stocks it.
- **Airport** links to its categories, its exclusives, and its editorial.
- **Brand** links to each airport where it is stocked and to its category.
- **Category** links to its brands and to the airports that stock it best.

No page should be more than three clicks from the home page.

## 8. Canonicals, pagination and what stays out of the index

- One canonical URL per thing. Filtered and sorted views canonicalise to the
  clean page; they are for people, not for indexing.
- The wine and whisky categories are large enough to need pagination. Each page
  canonicalises to itself, with proper previous and next relationships, rather
  than everything pointing at page one.
- Airport, brand and category-at-airport pages are small enough not to need
  pagination at this scale.
- Deliberately not indexed: the client review page, the proposal page, the demo
  controls, and the internal data-sources page. They are already marked so.
- The sitemap is generated from the database, so a page exists in it only if it
  exists on the site and passes the thresholds above.

## 9. Two things that must be fixed before brand pages ship

These are real and I would rather raise them now than have you find them:

1. **Brand names need normalising.** The retailers supply them inconsistently:
   we currently hold "Don Julio", "Don Julio Tequila" and "Don Julio®" as three
   separate brands, and "Moet & Chandon" alongside "Moët & Chandon". Left
   as-is, each would generate its own brand page competing with the others.
   This has to be resolved first.
2. **259 products have no category**, so they would fall out of the category
   structure entirely. They need classifying before the category pages are
   worth publishing.

## 10. What I need from you

1. Does the URL structure in section 2 work, and specifically do you agree that
   category-at-airport deserves to be a page type?
2. Are the thresholds in section 3 right, or would you set the brand cut-off
   somewhere other than three products?
3. Do you agree with leaving brand-and-airport as a table on the brand page
   rather than 633 separate pages?
4. Anything you want on these pages for search that is not in section 4.

If it is easier to comment inline, everything on the site has a comment thread
against it and I read them as they come in.
