Shop Module

Stakeholder Overview

What the Shop Does

The Shop is the product discovery and purchasing section of the site. It covers three distinct surfaces:

  • Shop landing (/shop) — the top-level catalogue showing all main product categories.
  • Category pages (/shop/{category-slug}) — product grids scoped to a single category, with subcategory filter tabs.
  • Product detail pages (PDP) (/shop/{category-slug}/{product-slug}) — full product information, flavour/variant selection, pricing, and add-to-cart.

All three surfaces are pre-generated at build time and kept fresh via Incremental Static Regeneration (pages refresh in the background every 5 minutes without a full deployment).

How Content Is Managed

Two systems divide responsibilities:

System Responsibility
Builder.io Page layout, product display order, product visibility whitelists, editorial overrides (badge text, CTA label/URL), category images and copy
Magento Live product data — names, prices, images, stock status, colour variants, flavour attributes, bundle options

Content editors work exclusively in Builder.io. Developers update structural templates and Builder.io component registrations. Neither team needs to deploy code to change what products appear or how they are presented.

Key Features

  • Province-based product filtering — on markets where it is enabled, products are shown only when the logged-in user's province matches the product's province list. This is transparent to the user.
  • Discontinued product redirect — when a product's URL is removed from Magento, users following old links land on the shop with a contextual banner explaining the product is discontinued and the closest category is pre-selected.
  • Sticks and consumables matrices — device accessories (sticks/tabs) and consumable products use a structured matrix sourced from Builder.io that groups flavours and variants into a single coherent selection UI, instead of individual product cards.
  • Free-trial products — a parallel Magento store provides trial-specific SKUs with their own cart flow. Trial products are sourced from a separate store view and have a dedicated page URL pattern.
  • GTM analytics — every product list view fires a view_item_list ecommerce event automatically when the Google Tag Manager container initialises.

Module: Shop

Source: apps/jti-next/src/app/[locale]/(default)/shop/

Route Structure

Route File Page component Description
/[locale]/shop shop/page.tsx ShopRootPage Top-level catalogue
/[locale]/shop/[slug] shop/[slug]/page.tsx ShopCategoryPage Single-level category grid
/[locale]/shop/[...slug] shop/[...slug]/page.tsx ShopProductPage Product detail page (catch-all)

Rendering Strategy

All three routes share the same ISR configuration:

export const dynamic = "auto";
export const dynamicParams = true;
export const revalidate = 300; // 5 minutes

Static paths are pre-generated at build time, gated by two environment variables:

Variable Required value Controls
NEXT_PUBLIC_SSG_CACHE_ENABLE "1" Enables static generation entirely
PRERENDER_MAGENTO_PAGE_GROUPS comma-separated list Which groups are pre-rendered

Valid PRERENDER_MAGENTO_PAGE_GROUPS values relevant to the shop:

Value What it generates
shop Locale-only paths for the root shop page
shop-subcategories { locale, slug } for each top-level category
categories { locale, slug[] } for category pages discovered via Magento
products { locale, slug[] } for all whitelisted product pages

Data Flow

Request
  │
  ▼
Route entry (page.tsx)
  ├─ generateMetadata()  ← resolveShopMetadata / Builder.io SEO data
  └─ <ShopRootPage | ShopCategoryPage | ShopProductPage>  [React Server Component]
        │
        ├─ ShopRootPage / ShopCategoryPage
        │     └─ ProductsContent  [RSC]
        │           └─ fetchCategoryAndContentData()
        │                 ├─ getGlobalConfiguration()           (tenant Config)
        │                 ├─ getSticksMatrix / getConsumablesMatrix  (Builder.io)
        │                 ├─ getAllProductsAggregations()        (Magento GraphQL)
        │                 ├─ prepareSticksMatrix / prepareConsumablesMatrix
        │                 ├─ fetchOneBuilderEntry("category")   (Builder.io)
        │                 ├─ fetchWhitelistedProducts()         (Builder.io global whitelist)
        │                 ├─ getCategoryProducts() × subcategory  (Magento GraphQL, paginated)
        │                 ├─ getAllProductsColorMap()
        │                 ├─ getOrderedWhitelistedProducts() → getOverriddenProducts() → getUniqueProducts()
        │                 └─ getFilteredSkus() per subcategory  (province-aware)
        │           └─ Builder.io <Content model="category">
        │                 ├─ CategoryFilters   (subcategory filter tabs)
        │                 └─ CategoryItems     (product card grid)
        │                       └─ ProductCard × n
        │
        └─ ShopProductPage
              └─ magentoMiddleware pipeline
                    └─ ConfigurableProduct | BundleProduct | ConsumablesProduct  [RSC]
                          └─ Builder.io <Content model="product">

Internal Utilities

The _utils/ directory contains helpers grouped by concern:

Group Utilities Purpose
Static params generateStaticParamsForShopPage, generateStaticParamsForCategoryPage, generateStaticParamsForProductPage Build-time path generation, respects ssgCacheEnable and prerenderMagentoPagesGroups
Slug / locale getLocalizedSlugParams, getLocalizedNormalizedSlugParams Produce { locale, slug } pairs across all configured locales
Category resolution getCategoryItem, getRootCategoryWithMainCategories, isCategoryChild Disambiguate Magento category results; fetch root + subcategory tree
Product ordering productsOrderedWhitelist (getOrderedWhitelistedProducts, getProductsOrderedWhitelist, hasOrderedWhitelist) Filter and sort products per Builder.io-defined SKU whitelist
Product deduplication getUniqueProducts Remove duplicate url_key entries; optionally apply province filter
Province checkProductProvinceToMatchUser, getProductProvince, getProductProvinceArray, getFilteredSkus Parse comma-separated province strings; filter SKU lists to user's province
Product overrides getOverriddenProducts Merge Builder.io editorial fields (badge, button overrides) onto Magento product data
Matrices prepareSticksMatrix, prepareConsumablesMatrix Sanitize and enrich matrix data from Builder.io for rendering
Discontinued products fetchDiscontinuedProductName Fetch product name by SKU for the discontinued product banner
SEO getPageLanguageAlternates Build hreflang alternates — branches between product pages and static pages
GTM viewItemListEventDataFactory Build view_item_list GTM ecommerce payload from deduplicated product list
Data extraction extractSkus Pull non-null SKUs from GraphQL product arrays
Validation ProductDataOverride.schema.ts Zod schema for Builder.io product override content entries

Referenced Components

Environment Variables & Feature Flags

Runtime environment variables (env.ts)

Variable Type Default Description
NEXT_PUBLIC_BUILDER_API_KEY string "" Builder.io public API key
NEXT_PUBLIC_MAGENTO_GRAPHQL_ENDPOINT string "" Magento GraphQL endpoint URL
NEXT_PUBLIC_REWRITE_CONFIG JSON string {} Per-locale URL rewrite map (shop, shop-province, etc.)
NEXT_PUBLIC_ROOT_CATEGORY_UID string "" Magento UID of the root category (default "NDE=")
NEXT_PUBLIC_AVAILABLE_MAGENTO_STORES string "" Comma-separated list of store view codes
NEXT_PUBLIC_SSG_CACHE_ENABLE "1" \| other disabled Enables generateStaticParams for shop routes
NEXT_PUBLIC_PROVINCE_ENABLED "1" \| other disabled Gates province-based product filtering
PRERENDER_MAGENTO_PAGE_GROUPS comma-separated [] Which Magento page groups to pre-render (products, categories, shop, shop-subcategories)
PRERENDER_BUILDER_PAGE_GROUPS comma-separated [] Which Builder.io page groups to pre-render

Tenant Config keys (Builder.io commerceGlobalSettings model)

These are managed per-market in Builder.io and resolved via getGlobalConfiguration() / getCommerceGlobalSettings().

Config key Type Default Used by
BRAND_CONSUMABLES_FEATURE_FLAG boolean fetchCategoryAndContentData — enables fetching the brand consumables matrix
isConsumablesMatrixEnabled boolean false fetchCategoryAndContentData — enables consumables matrix rendering on category pages
hideTeaserConfigurableOptions boolean false ProductCard — hides colour swatches on product listing cards
isQuickAddToCartCtaForSimpleSticksEnabled boolean false ProductCard, CategoryItems — shows a quick add-to-cart button on stick product cards
DISABLE_COMMERCE boolean false Disables all commerce features (prices, cart) site-wide
FREE_TRIAL_FEATURE_FLAG string Controls free-trial product visibility
defaultProductCtaLabel string "" Fallback CTA label on product cards
defaultProductCtaUrl string "" Fallback CTA href on product cards
enableSubscriptions boolean false Enables subscription purchase format
displayOutOfStockVariants boolean false Whether to show out-of-stock colour variants
displayOutOfStockBundleOptions boolean false Whether to show out-of-stock bundle options
displayOutOfStockStickProducts boolean false Whether to show out-of-stock stick products

Tenant Config keys (Builder.io productDetailPageConfiguration model)

Config key Type Default Used by
sortByProductAttribute boolean false fetchCategoryAndContentData, ConfigurableProduct, BundleProduct — sorts colour/flavour swatches by the aggregation order defined in Magento
productSpecification { specification: string }[] ConfigurableProduct, BundleProduct — renders a specification list on the PDP

Commerce Library: products.ts

Source: apps/jti-next/src/libs/commerce/products.ts

Shared server-side library used by the shop module, product templates, and any Builder.io content that needs to resolve product data from Magento. It handles colour-variant extraction, swatch sorting, SKU→ProductCardItem mapping, gallery asset preparation, and colour-map assembly for sticks/consumables matrices.

Exported Types

Type Description
ColorVariant A single colour swatch: { label: string; value: string; valueIndex?: number \| null }
ProductCardItem Resolved product data for a single card: SKU, title, image, URL, colours, category IDs/names, stock status, subscription format, trial flags
ProductsMap Record<string, ProductCardItem> — keyed by SKU; the return type of getProductsFromContentBySkus and getProductsFromContent
ProductFragment Minimal Magento product shape (sku, __typename, url_key) used for colour assignment
VariantWithAttributes Lightweight variant shape used when checking attribute match: { attributes?: Array<{ code, value_index } \| null> \| null }

Exported Functions

Colour / swatch utilities

Function Signature Description
hasValueIndex (value) => value is T & { value_index: number } Type guard — returns true when value_index is a non-null number
hasMatchingVariant (valueIndex, attributeCode, variants) => boolean Returns true when at least one purchasable variant carries the given attribute value
toColorVariant (value) => ColorVariant \| null Converts a raw ConfigurableProductOptionsValues entry to ColorVariant; returns null when hex or label is missing
sortValuesByAggregationOrder (values, attributeCode, aggregations) => Array<T \| null> Sorts configurable option values by the display order defined in Magento aggregations
sortConfigurableOptionValues (values, variants, attributeCode?, sortByProductAttribute?, aggregations?) => Array Sorts option values either by aggregation order (when sortByProductAttribute is true) or by variant_position
extractColorsFromConfigurableProduct (product, sortByProductAttribute?, aggregations?) => ColorVariant[] \| undefined Extracts swatches from a configurable product; filters to only colours that have a purchasable variant
getSimpleProductsColorMap async (products, locale) => Map<string, ColorVariant[]> For each SimpleProduct, fetches its parent configurable product and resolves the matching colour swatch
getAllProductsColorMap async (products, locale, isWithoutColor, sortByProductAttribute?, aggregations?, sticksMatrixData?, consumablesMatrixData?) => Map<string, ColorVariant[]> Builds the full SKU→colours map for a product list; merges configurable, simple, sticks-matrix, and consumables-matrix colour sources

Product discovery

Function Signature Description
isProductNew (newFromDate?, newToDate?) => boolean Returns true when the current date falls within the product's new_from_date / new_to_date window
getProductsFromBlocks (blocks, skus?, trialSkus?) => { skus, trialSkus } Recursively walks Builder.io block tree collecting SKUs from ProductCard and SimpleProductTeaser blocks
getProductsFromContentBySkus async ({ skus, trialSkus, locale, isWithoutColors, sortByProductAttribute?, sticksMatrixData?, consumablesMatrixData? }) => ProductsMap Fetches product data from Magento for the provided SKU lists, assembles colour maps, and returns a ProductsMap
getProductsFromContent async ({ locale, content, isWithoutColors?, sortByProductAttribute?, sticksMatrixData?, consumablesMatrixData? }) => ProductsMap Convenience wrapper — extracts SKUs from Builder.io content blocks and delegates to getProductsFromContentBySkus

PDP utilities

Function Signature Description
getGalleryAssets (items: media_gallery) => GalleryAsset[] Normalises a Magento media gallery into a sorted flat array of { label, path, previewPath, position, type, role } items; separates images from video URLs

Internal (non-exported) helpers

These are private to the module and not part of the public API:

Helper Description
assignColorToSimpleProduct Fetches the parent configurable product for a SimpleProduct and resolves its specific colour
getSticksMatrixColorsMap Builds a SKU→colours map from a sticks matrix, respecting aggregation sort order
getConsumablesMatrixColorsMap Builds a SKU→colours map from a consumables matrix, grouping by brand/packaging/purchase-type
getProductsFromMagento Low-level Magento GraphQL call; returns { items, aggregations }
findColorOption Finds the color attribute in a configurable product's options array

Components

Each component has its own dedicated document with full props tables, Config key references, Builder.io inputs/bindings, and UI primitive details.

Component Role Document
ProductCard Builder.io-registered product listing card components/ProductCard.md
CategoryItems Responsive product card grid for a category components/CategoryItems.md
CategoryFilters Subcategory filter tabs components/CategoryFilters.md
ProductCardContainer Builder.io container managing card grid or carousel components/ProductCardContainer.md
Category Page template wiring Builder.io "category" model to data components/Category.md
ConfigurableProduct PDP template for colour/variant configurable products components/ConfigurableProduct.md
BundleProduct PDP template for bundle/starter-kit products components/BundleProduct.md
ConsumablesProduct PDP template for consumable products with flavour matrix components/ConsumablesProduct.md

ProductCard

See components/ProductCard.md

The app-level ProductCard is a Builder.io-registered wrapper. It reads product data from a products binding injected by the parent ProductCardContainer, then delegates rendering to the @jti/ui ProductCard primitive.


CategoryItems

See components/CategoryItems.md

Renders a responsive grid of product cards for a single category. Builder.io registration: name "Category items", model "category". Optionally inserts an RtbCard at a configurable grid position.


CategoryFilters

See components/CategoryFilters.md

Renders subcategory filter tabs. Builder.io registration: name "Category filters", model "category". Integrates with FilterContext via useHandleFilters. Uses @jti/ui/eva-components/CategoryFilters (deep import).


ProductCardContainer

See components/ProductCardContainer.md

Builder.io container (canHaveChildren: true) that manages shared dynamic product data for child ProductCard components and renders either a static grid or a Swiper carousel.


Category

See components/Category.md

Page template that wires the Builder.io "category" model to server-fetched data. Provides FilterContext and passes categoryName, categoryItems, categoryFilters, and subcategoriesMap as Builder.io content bindings.


ConfigurableProduct

See components/ConfigurableProduct.md

PDP template (RSC) for configurable products (e.g. a device in multiple colours). Fetches from Magento and passes structured data into a Builder.io "product" model entry.


BundleProduct

See components/BundleProduct.md

PDP template (RSC) for bundle/starter-kit products. Follows the same pattern as ConfigurableProduct but adds productIsBundle: true, productBundlesConfig, and optional isTrial support.


ConsumablesProduct

See components/ConsumablesProduct.md

PDP template (RSC) for consumable products with multi-flavour matrices. Fetches via a dedicated "consumables" product type Builder.io entry. Uses @jti/ui ConsumablesFormStepSection and MultipleFlavorsSelector / MultibrandMultipleFlavorsSelector.