← All case studies
PLPNext.js · React SPAEXP-05Aug 2026

SA daily-deals marketplace · Daily deals, South AfricaA promo banner that sits after exactly sixteen products

Place one banner after the sixteenth card, aligned to a row end, on a virtualised, lazy-loaded grid with mixed-width tiles. Simple brief, hostile page.

ClientSA daily-deals marketplace
PlatformNext.js · React SPA
PagePLP
Measured onBanner CTR, Promo page visits, Revenue
Observation
  • The grid mixes 33, 25 and 50 percent tiles, so 'after sixteen' is not a fixed pixel or DOM position.
  • Page data included null and dead slots that counted as products, pushing the banner mid-row and cascading holes down the grid.
  • The testing tool re-executes on every SPA navigation, so any run-once guard had to be placed deliberately.
Hypothesis

An in-grid banner at a natural scroll depth will drive traffic to the promotional deals page without disrupting browsing.

What I built
  • An idempotent placement routine that re-validates every 400 ms, stops early once the DOM card count matches the data count, and hard-stops at twelve seconds.
  • Row detection by measuring flex-item wrapper positions with an eight-pixel tolerance, hiding the banner during measurement so it cannot distort the rows it measures.
  • Product data read from the router's live page props, which stay fresh on SPA navigation, rather than the stale initial payload.
  • A debounced mutation observer on the container that restores the banner when the framework remounts the list.
The hard parts

Create, never move

Placement returns both an element and a confidence flag. Fallback paths may create the banner if nothing exists, but only a confident measurement is allowed to move it.

Counting real products

Filtering the page data to entries with an ID fixed the off-by-one that parked the banner mid-row.

Outcome

Measured on banner ctr, promo page visits, revenue. Test results belong to the client and are shared in conversation, with their permission, rather than published here.

More experiments

View all →