Simon & Schuster
A 300,000-product headless bookstore serving two regions off one Shopify backend.
- Catalogue size
- 300,000+ products
- Regions
- US & UK
- E2E coverage
- Critical paths in Cypress
I'm Thang — a senior Shopify and full stack engineer with 9+ years shipping production commerce. Most recently a 300,000-product headless Shopify bookstore for Simon & Schuster across US and UK, and a WooCommerce store holding 20–30 orders a minute at peak.
I'm a full stack engineer who specialised into commerce — specifically the part where a storefront meets a platform and things get difficult. Headless Shopify at 300,000 products. WooCommerce at 20–30 orders a minute. An app that installs natively on three platforms with incompatible checkout models.
The work I'm best at sits on the seams: the Storefront API boundary, the checkout hand-off, the inventory sync that silently drifts, the event pipeline that must never pay a partner twice. These are the problems that look small in a ticket and turn out to be the whole project.
I care about what happens after launch. Conversion data, Core Web Vitals on real devices, and Cypress coverage on the paths that cost money when they break — because a storefront that ships and then quietly loses 3% of its checkouts is not a successful project.
I hand checkout back to Shopify rather than rebuilding it. A custom checkout costs more in PCI scope, payment methods and lost Shop Pay conversion than it will ever earn back.
On a store doing 20–30 orders a minute, the obvious fix was caching the catalogue. The actual bottleneck was the order-write path. Profiling first is not process — it is the difference between fixing it and not.
Webhooks retry, pipelines redeliver, users double-click. Idempotency is not a nice-to-have on money paths — it is the design.
Headless is a trade: rendering control for routing, caching and preview complexity. I go headless when the catalogue or the experience demands it, and stay on a theme when it does not.
Open to
Every case study covers the problem, the architecture, the parts that went wrong, and what changed for the business.
A 300,000-product headless bookstore serving two regions off one Shopify backend.
A WooCommerce store taking 20–30 orders a minute at peak without falling over.
One shipping-protection product, installed natively across three commerce platforms.
Cross-domain affiliate attribution that stays correct under real event volume.
Live video streaming on AWS MediaLive and CloudFront.
Not a badge list. Each of these is a decision I have had to make on a production store, and the reasoning behind it.
Shopify keeps the commerce. You get full control of rendering, routing and caching.
Going headless is a trade, not an upgrade. You gain rendering control, edge caching and a component model that a Liquid theme cannot give you — and you take on routing, cache invalidation, preview and a checkout you no longer own end to end. I have shipped this on a 300,000-product catalogue across two regions, which is where the trade actually gets tested: the moment your catalogue outgrows what a theme can paginate, headless stops being a preference and becomes the requirement.
What you get
A storefront whose performance ceiling is set by your architecture rather than by theme limits.
The customer-facing read path — products, collections, cart.
The Storefront API is deliberately scoped to what a customer may see and do. The engineering that matters is in the shape of your queries and the boundaries of your cache: over-fetch and every page pays for it, under-fetch and you waterfall. At 300k products, cursor pagination has to be treated as a streaming problem with cached boundaries rather than a list you can walk on demand.
What you get
Fast catalogue and cart surfaces that stay fast as the catalogue grows.
query CollectionPage($handle: String!, $cursor: String) {
collection(handle: $handle) {
products(first: 24, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
handle
title
featuredImage { url altText width height }
priceRange {
minVariantPrice { amount currencyCode }
}
}
}
}
}Everything the storefront is not allowed to do.
Inventory reconciliation, metafield-driven merchandising, bulk catalogue operations, order back-office work. The Admin API is where rate limits become a design constraint rather than a footnote — Shopify's cost-based throttle means a naive bulk job dies halfway through and leaves you in a partial state. Bulk operations and idempotent, resumable jobs are the difference between a sync that works and a sync that works on a small store.
What you get
Catalogue and inventory operations that survive 300k products and partial failure.
async function adminRequest<T>(query: string, variables: object): Promise<T> {
const res = await shopify.request(query, variables);
// Shopify returns the leaky-bucket state on every call. Slow down
// *before* being throttled, not after a 429.
const { currentlyAvailable, maximum } = res.extensions.cost.throttleStatus;
if (currentlyAvailable < maximum * 0.2) {
await sleep(1000);
}
return res.data;
}Hand checkout back to Shopify. Deliberately.
Checkout is the one surface where building your own costs far more than it earns — PCI scope, payment methods, fraud, tax, and Shop Pay conversion rates you will not beat. The Buy Button SDK lets a headless storefront hand off cleanly. The real skill is debugging the boundary: when a hand-off fails, the failing surface belongs to Shopify, so you diagnose it with pixel and network-level evidence to separate your bug from platform behaviour.
What you get
Shopify's conversion-optimised checkout, with hand-off failures that are diagnosable rather than mysterious.
Accurate checkout events in a sandboxed world.
Since Shopify moved pixels into a sandbox, the old approach of injecting scripts into checkout is gone. A Custom Pixel subscribes to Shopify's standard event stream from inside that sandbox — which means your analytics gets trustworthy `checkout_completed` and `purchase` events instead of the inferred, lossy ones that make every funnel report an argument.
What you get
A funnel report the marketing team can actually make decisions from.
analytics.subscribe("checkout_completed", (event) => {
const checkout = event.data.checkout;
gtag("event", "purchase", {
transaction_id: checkout.order.id,
value: checkout.totalPrice.amount,
currency: checkout.currencyCode,
items: checkout.lineItems.map((item) => ({
item_id: item.variant.sku,
item_name: item.title,
price: item.variant.price.amount,
quantity: item.quantity,
})),
});
});Instrumentation that survives ad blockers and consent.
GA4's event model is a genuine break from Universal Analytics, and most stores are still reporting badly because of it. I set up GTM as the single control plane, define the ecommerce event schema once, and wire it through both the storefront and the Custom Pixel so client-side loss and consent state are accounted for rather than quietly discarded.
What you get
Attribution and funnel data you can defend in a meeting, not numbers that disagree with Shopify's own reports.
Core Web Vitals on real devices, not on your laptop.
Storefront performance is mostly a discipline of not shipping things: image weight, third-party tags, hydration cost, and render-blocking work in that order. On commerce sites the biggest wins are usually LCP image handling and getting the marketing tag stack under control — a store can have perfect application code and still fail Core Web Vitals because six tags load synchronously in the head.
What you get
Faster storefronts, better organic ranking signals, and fewer people leaving before the page renders.
Shipping is half the job. Knowing if it converts is the other half.
Speed is a conversion lever, but only one. The rest is in the funnel: where carts are abandoned, which variants confuse people, what the checkout hand-off costs you. With the funnel properly instrumented, CRO stops being redesign-by-opinion and becomes a sequence of changes you can actually measure.
What you get
Changes justified by funnel data instead of by taste.
The tests that exist because something expensive broke once.
On a commerce site you do not need to test everything — you need to test what costs money when it breaks. Add to cart, checkout hand-off, region switching, inventory display. Those run in CI on every change, because the alternative is finding out from a customer that checkout has been broken since Tuesday.
What you get
Revenue-path regressions caught before deploy, not after a lost sale.
it("hands off to Shopify checkout with the correct cart", () => {
cy.visit("/products/the-midnight-library");
cy.findByRole("button", { name: /add to cart/i }).click();
cy.findByRole("link", { name: /checkout/i }).click();
// The hand-off is the boundary that breaks in production.
cy.origin("https://checkout.shopify.com", () => {
cy.contains("The Midnight Library").should("be.visible");
});
});06
Remote · US & UK storefronts
Headless Shopify storefronts for a 300,000+ product publishing catalogue across two regions.
05
Remote
Performance, reliability and integration work on a store peaking at 20–30 orders per minute.
04
Remote
Shipping-protection app with subscription billing, shipped natively to Shopify, Magento and BigCommerce.
03
Remote
Affiliate platform with cross-domain attribution and an idempotent event-processing pipeline.
02
Remote
Live streaming platform on AWS MediaLive and CloudFront with Redis-backed live state.
01
Vietnam
Commerce and web application work across the JavaScript and PHP ecosystems — the foundation the Shopify specialisation was built on.
Highlighted items are where I have the most production depth — the rest I use comfortably.
Where most of the last nine years went. Shopify deeply, plus the platforms merchants actually migrate from.
Storefronts that render fast on real devices and real networks, not just on a laptop.
The services behind the storefront — integrations, event processing, and the money paths.
Enough platform depth to own a deploy, a queue and a database — not just consume them.
Shipping a storefront is half the job; knowing whether it converts is the other half.
The tests that exist because something expensive broke once.
0+
Years shipping production software
Commerce platforms, APIs and infrastructure since 2016.
0K+
Products served headlessly
The Simon & Schuster catalogue, across US and UK storefronts.
0/min
Peak order throughput handled
Sustained without checkout degradation on a high-traffic store.
0
Commerce platforms shipped natively
Shopify, Magento and BigCommerce from one product core.
Took a 300,000-product catalogue headless across two regions without a big-bang cutover, on a store that was transacting the entire time.
Built the pixel and network-level evidence trail that separates your bugs from Shopify's behaviour — the single hardest part of running headless in production.
Continuous, idempotent warehouse reconciliation on a WooCommerce store selling faster than its sync was designed for.
Idempotent event processing for affiliate attribution, where a duplicated event is a real overpayment to a partner.
A platform-agnostic core with thin adapters, so Shopify, Magento and BigCommerce ship the same feature once.
Rebuilt affiliate attribution on first-party mechanisms when the industry default stopped working.
Short pieces on the commerce problems that don't have a Stack Overflow answer.
May 14, 2026 · 2 min
When a headless checkout hand-off fails, the failing surface belongs to Shopify. Here's how to tell your bug apart from platform behaviour.
ReadMarch 2, 2026 · 2 min
Cursor pagination is fine at 500 products and painful at 300,000. What changes when your catalogue outgrows the happy path.
ReadJanuary 20, 2026 · 2 min
Every webhook arrives twice eventually. On money paths, that's not an edge case to handle later — it's the shape of the system.
ReadI reply to every message that isn't a template. If you're hiring for a senior Shopify or full stack role — or you have a headless build that's gone sideways — tell me what you're working on.
Based in Vietnam (GMT+7) · Working remotely with US, UK and EU teams