DEV Community

OPTTOYSCHINA
OPTTOYSCHINA

Posted on

Stale-while-revalidate on the edge: how our marketplace killed the cold start (and kept geo-currency)

"Cold start" is not a law of nature. It's a caching strategy you haven't finished. Here's how our SSR marketplace serves a warm copy from ~300 datacenters — to every visitor, always, including the very first one after a deploy.

Context: B2B toy marketplace, 389,000 products, mobile PageSpeed 93 on the full site. Receipts and the whole story here.

The two classic pains

SSR pain: while the page renders on the server, the user stares at a blank screen. Cache pain: the copy expires once a day, and some unlucky visitor pays for the rebuild.

Both die with one construct in the edge middleware — stale-while-revalidate:

  1. Request comes in → serve the cached copy instantly, even if it's stale.
  2. In the background (ctx.waitUntil), rebuild the page and store the fresh one.
  3. s-maxage=86400 + Tiered Cache + Cache Reserve: the copy lives a day, regional PoPs pull from the upper tier, the reserve keeps a central fallback.

Nobody ever waits for a render. FCP is a stable 1.4s from any point on the globe.

The dilemma everyone solves wrong: personalization vs. caching

A marketplace must show tenge in Almaty, som in Tashkent, dollars in Berlin. The usual answer — server-side geo rendering — means one cached copy per country, most of them permanently cold. Congratulations, your cache is decorative.

Our answer: fully neutral SSR. One HTML for the whole world → one cache → always hot. On the client:

// the CDN already knows the geo — ask it for free
fetch('/cdn-cgi/trace')  // → country=KZ → sessionStorage
// prices in HTML are a neutral base; the client applies the local rate
// BEFORE prices paint — no flash of wrong currency
Enter fullscreen mode Exit fullscreen mode

Personalization lives, and so does the cache.

A living storefront that doesn't kill the API cache

We wanted the homepage to rotate bestsellers every 5 minutes. Random offset per user = cache murder. Deterministic window = free liveliness:

window = floor(now / 5min) % pool   // every user on Earth gets the same slice
Enter fullscreen mode Exit fullscreen mode

Redis serves it from memory; the storefront breathes at zero cost. (One follow-up rake: the window must respect your anti-bot depth cap — invariant: window ≤ min(pool, cap) — or the block silently starves once in a while.)

The honest caveat

1 PageSpeed run in 5 still catches a 77: the LCP poster on a rare ice-cold PoP. We inlined a 6 KB data-URI webp poster and deferred the 4 MB hero video past window.load — the tail is almost dead, but not fully. We publish that number anyway; that's the difference between a measurement and marketing.

Verify, don't trust

Open DevTools on opttoyschina.com — every search request returns an open Server-Timing header, and pasting the domain into pagespeed.web.dev takes 30 seconds. Questions on the middleware, cache keys, or the deterministic rotation — comments are open.

Top comments (0)