← All projects

MoosanClub

Production e-commerce platform with server-side rendering, authentication, Stripe payments, and a hand-rolled request cache.

Role
Lead frontend engineer
Year
2024
  • Next.js
  • TypeScript
  • Stripe
  • SSR
  • PostgreSQL

Problem

MoosanClub needed a storefront that felt instant on first load and stayed correct under real traffic: authenticated carts, live inventory, and Stripe checkout that could never double-charge. The earlier build leaned entirely on client-side fetching, so the first paint was a spinner and search engines saw an empty shell.

  • First contentful paint gated behind a client-side data waterfall
  • Cart and inventory state drifting out of sync across tabs
  • No server-rendered product pages for SEO or social previews

Approach

I rebuilt the product and category pages as server-rendered routes so the meaningful content ships in the first HTML response, then hydrated only the interactive islands — cart, quantity steppers, checkout.

Rather than pull in a heavy data-fetching dependency, I wrote a small SWR-style cache keyed by request, with revalidation on focus and mutation. It kept the client bundle lean while giving carts optimistic updates that reconcile against the server.

  • SSR product/category pages; interactive islands hydrated selectively
  • Hand-rolled stale-while-revalidate cache — no extra runtime dependency
  • Stripe checkout with idempotency keys to make retries safe

Tradeoffs

Writing the cache by hand meant owning invalidation logic that a library would hand you for free. The payoff was a materially smaller bundle and full control over revalidation timing; the cost was more tests and a higher bar for onboarding new contributors.

Server-rendering everything shifted load onto the origin, so I had to be deliberate about caching headers and which routes could be statically generated versus rendered per request.

Outcome

Product pages went from a spinner-first experience to server-rendered content on first byte, and checkout reliability improved once retries became idempotent. The lean cache kept the JavaScript budget well under the target while still feeling live.

  • Server-rendered content on first response across the catalogue
  • Idempotent checkout — retries no longer risk double charges
  • Client bundle kept small by avoiding a data-fetching dependency