Paris--:--:--
Work
03Product & UICompany websiteLive

KCX by GETRAC website

A showcase website, with a catalogue covering more than 2,600 active products, synchronised from a PIM dataset.

Type
Company website
Status
Live
Timeline
2025
Role
Design, front-end and back-end development, deployment
Team
Solo builder with marketing, sales and product-data stakeholders
Context
Built as part of my role at KCX by GETRAC
Impact
  • P95 product-grid endpoint latency around 80 ms after caching and materialised views
  • Account-free product lists that can be shared, exported or printed
  • More than 2,600 active products synchronised from a broader PIM dataset
Stack
Figma · Next.js · PostgreSQL · Qdrant · Redis · Cloud Run · OpenAI API
KCX by GETRAC website: project visual

Context

KCX sells more than 2,600 active products across eleven families, through three brands, to professional buyers: garages, dealerships and detailing shops. They do not sell online. The site's job is discovery and specification, not checkout: help a buyer find the right product, understand it, and hand a precise selection to a salesperson.

That makes it an unusual brief. All the machinery of e-commerce, search, filters, comparison and a cart are useful, but the destination is a conversation rather than a payment.

Constraint

Three things pulled against each other. The catalogue had to feel like a considered brand object rather than a database dump. Product data changes continuously in the PIM, so nothing could be hand-maintained. And the buyers who matter most are often standing in a workshop on a phone with one hand free.

A periodic table as the homepage hero

The homepage replaces a hero banner with a periodic-table visualisation of the catalogue. Ninety-nine hand-picked products are laid out as elements, grouped in columns by range and in three blocs by brand, borrowing the visual grammar of the KCX umbrella brand: element tiles, symbol abbreviations, colour-coded families, a legend that toggles families on and off.

Product family view in the catalogue

Set aside, then share

Buyers can set products aside while browsing, a cart with no checkout. State lives in local storage, so there is no account, and it syncs across tabs through the native storage event. Once two or more products are set aside they can be compared by the AI panel, or turned into a durable public URL.

That share flow is the part that earns its place. A salesperson assembles a selection, adds a per-product comment such as "need this in the 5 L size", and sends a link. The recipient sees a standalone page they can export to Excel or print as a clean quote sheet. No login on either end. Here is a real one.

Product detail modal with technical specifications
Shared product list page with per-product comments

Search runs cheap-and-precise first, expensive-and-semantic only as a fallback. A tuned fuzzy matcher handles typos and French accents, weighted across name, subtitle and product code. If that does not return enough confident matches, the query is embedded and searched against a vector database.

Multi-word queries get handled separately: each keyword is embedded and searched independently, then the ranked lists are merged with reciprocal rank fusion, with a boost for products matching every term. So "tache carrosserie" rewards products that satisfy both words over one that strongly matches only the first.

Ranking also accounts for how complete a product record is. A decent keyword match on a richly documented product outranks a technically closer match on a near-empty one, because the second one is a dead end for the buyer.

Key decisions

  1. Build the mobile zoom instead of using the browser's

    On narrow screens the whole periodic grid is scaled down to fit, which makes tiles too small to tap. Native pinch-zoom would have zoomed the entire page, including the modal that opens on tap. So the component runs its own zoom-and-pan viewport: first tap zooms 2.4× around the tapped tile, second tap opens the product, dragging pans with edge-clamping. More code than deferring to the browser, but the browser's behaviour was actively wrong here.

  2. Move the sync off the platform rather than fight the timeout

    A full PIM sync takes seven to thirteen minutes, well past the serverless function limit. Rather than shard it into artificial chunks, the sync moved to a dedicated worker that can run for an hour, answers a trigger with an immediate acknowledgement, and holds a distributed lock so two runs can never overlap.

  3. Self-host the product images

    Image optimisation on the hosting platform is quota-metered per account, and the periodic table alone renders ninety-nine product photos. Self-hosting those images was a cost constraint driving an architecture decision, not a technical preference.

  4. Select the sixteen columns the grid renders, not all forty

    The product-grid endpoint originally selected every column, including rich HTML descriptions and multiple photo arrays the grid never displays. Narrowing it to what is actually rendered cut the payload by around 75% with no functional change. The detail view fetches the rest on demand.

Impact

Live at getrac.fr, auto-deployed from main.

P95 endpoint latency measured during application profiling on the production dataset.

Endpoint
Product-grid query endpoint
Volume
More than 2,600 active products, 50-product response
Environment
Production dataset, Cloud Run and PostgreSQL
Before
≈500–800 ms · 250 KB payload per 50 products
After query + payload work
≈150–200 ms · 62 KB
After caching + materialised views
≈80 ms · 62 KB

The techniques were unglamorous and worked: column pruning, a smaller default page size, tiered caching with hit/miss logged so effectiveness is observable in production, indexes on the columns actually filtered on, a materialised view pre-aggregating filter counts, and static pre-rendering for the 500 most important product pages.

Limitations and next steps

The next steps are to add professional accounts and implement a proprietary online flipbook system.