Shop & products¶
Routes¶
| Route | Purpose |
|---|---|
/shop |
All products in popularity order, with a row of category chips |
/shop/fruits |
Fruits category (24) |
/shop/boxes |
Fruit Plug Boxes category (4) |
/shop/subscription |
Subscription category (3) |
/shop/merchandise |
Merchandise category (10) |
/shop/rare-special |
Rare & Special category (5) — new, post Phase 0 |
/p/[slug] |
Product detail page (PDP) |
Data flow¶
sequenceDiagram
participant Browser
participant Next as Next.js (Server Component)
participant Woo as Woo Store API
Browser->>Next: GET /shop
Next->>Woo: GET /wc/store/v1/products?per_page=100&orderby=popularity
Next->>Woo: GET /wc/store/v1/products/categories
Woo-->>Next: JSON (parallel fetches)
Next-->>Browser: SSR HTML · cached 5min (ISR tag `products`)
The Woo Store API is public, cookie-sessioned, and returns the same data the live WooCommerce admin sees. We parallelise the two fetches and server-render the grid.
How the PDP handles variable products¶
The Store API has a quirk: GET /products?slug=mangosteen can return a variation (with type: "variation") whose slug matches the parent's. We detect this and re-fetch by id to get the full parent record with attributes, description, price range. See packages/woo-client/src/index.ts:
export async function getProductBySlug(slug: string) {
const list = await wooFetch("/products", { searchParams: { slug, per_page: 10 }});
const parent = list.find(p => p.type !== "variation") ?? list[0];
return parent ? wooFetch(`/products/${parent.id}`) : null;
}
Related products¶
On the PDP, related products are fetched by querying the same category as the current product and excluding the current one, limited to 4. Better than Woo's default "randomly related" — semantic matching.
Variation display¶
Variable products (e.g. Mangosteen has Each / 0.5KG Box / 1KG Box) render their attributes as a section of clickable chips. Selecting a chip updates the Add-to-Cart payload:
The selector lives in components/product/ProductActions.tsx — a thin client island embedded inside the otherwise-server-rendered PDP.
Image handling¶
Product images come through Jetpack Photon (i0.wp.com). next.config.ts allows the *.wp.com and fruitplug.co.uk hostnames. Cloudinary is prepared but not yet in use.
Performance¶
Each page pre-fetches once and renders server-side. TTFB is ~50–100ms after warm cache. Client-side JS is minimal — only the ProductActions component ships with interactivity, everything else is static HTML + CSS.