E-commerce platform2026Tech lead, frontend + BFF9 months

Headless Commerce

Rebuilt a regional retailer's storefront as a headless Next.js app with an edge-cached BFF, taking checkout from a 4.1s LCP to under 1.5s during peak sale traffic.

  • Next.js
  • Node.js
  • Redis
  • PostgreSQL
1.4s
p75 LCP on mobile, down from 4.1s
-38%
checkout abandonment
12x
peak traffic handled without scaling the ERP

The problem

The legacy storefront was a server-rendered monolith. Every promotion caused cache stampedes on the product API, checkout timed out under load, and the marketing team waited on deploys for copy changes.

Constraints

Architecture

Key decisions

Decision 01
Split static and volatile data at the component level

Product content renders as cached Server Components with tag-based revalidation. Price and stock stream in through a tiny client island backed by the BFF.

Trade-off: Two fetch paths per page to reason about, in exchange for a fully cacheable shell.

Decision 02
Request coalescing in the BFF

Concurrent misses for the same SKU share one in-flight promise and one Redis write, which ended the stampedes during flash sales.

Trade-off: A slow upstream call now delays every waiting request, so it sits behind a 300ms timeout and a stale fallback.

Decision 03
Checkout as a state machine

Cart, address, payment and confirmation are explicit states with typed transitions, which made retries and back-button behaviour predictable.

Trade-off: More upfront modelling than a set of form pages.

In the code

bff/src/coalesce.ts
1const inflight = new Map<string, Promise<Product>>();23export async function getProduct(sku: string): Promise<Product> {4  const cached = await redis.get(`product:${sku}`);5  if (cached) return JSON.parse(cached);67  // Collapse concurrent misses into a single upstream call.8  let pending = inflight.get(sku);9  if (!pending) {10    pending = withTimeout(erp.fetchProduct(sku), 300)11      .then(async (product) => {12        await redis.set(`product:${sku}`, JSON.stringify(product), "EX", 60);13        return product;14      })15      .finally(() => inflight.delete(sku));16    inflight.set(sku, pending);17  }18  return pending;19}

What I learned