Origins
Origins sells whey protein and pre-workout supplements. The store is a custom storefront running in front of Shopify: the catalog, cart, and product experience are built as a standalone Next.js application, while Shopify keeps the commerce backend and the hosted checkout. I was the sole developer and owned the storefront end to end.
At a glance
- Role: Sole developer; owned the storefront end to end.
- Stage: Live headless store over Shopify.
- Core constraint: Build a custom storefront over Shopify without duplicating commerce state or rebuilding checkout.
- Strongest evidence: A strict server-state and client-state split between React Query and Jotai, a Shopify-authoritative cart, and a branded checkout through Checkout Extensibility and the Branding API.
Context
A Shopify theme is the fast path to a working store, and it comes with a ceiling. Layout, routing, and rendering behavior belong to the theme system, so anything the brand wants beyond it is worked around rather than built.
Going headless lifts that ceiling and moves work along with it. Products, collections, and the cart stop being template variables and become calls to an API that remains the source of truth for all of them. The storefront has to hold a correct view of data it does not own, and then hand the customer to a checkout it does not control without that transition feeling like leaving the site.
My role
I built the storefront application, designed the data layer against Shopify's Storefront GraphQL API, decided where cart state and interface state lived, and customized the hosted checkout to match the brand.
Shopify stayed the commerce backend throughout — products, inventory, orders, and payment remained on the platform, with the storefront as the customer-facing layer over it.
Architecture
The application is a Next.js storefront that talks to Shopify over the Storefront GraphQL API. Catalog data, collections, and the cart all originate there. Nothing about commerce is duplicated into a store of its own, because there is no second system that could be right when the two disagree.
Shopify stays authoritative, including for the cart. A cart is not a local list of items — it is a remote object with its own identity, line state, and pricing rules, and adding a line can change availability and totals in ways only Shopify can compute. A local copy of its prices and totals is easy to build and instantly becomes a second opinion about what the customer is buying, competing with the system that will actually charge them. So the storefront renders what the platform returned rather than what it predicted.
Stack
Storefront — Next.js 14 · TypeScript · Tailwind CSS
Data — Shopify Storefront GraphQL API · React Query · Jotai
Commerce — Shopify · Checkout Extensibility · Branding API
Engineering challenges
Two kinds of state that look identical in a component tree
The problem. A storefront holds state with very different owners. The cart lives in Shopify. Whether the cart drawer is open, which variant is selected, and which filters are applied live only in the browser. In a React tree they look the same, and treating them the same way pulls remote state into a client store where it starts drifting from the server that owns it.
Why it was hard. The convenient answer — one global store for everything — fails in both directions. Cart mutations are not simple writes whose outcome the client can anticipate, so a client-owned cart needs reconciliation logic that exists only because the wrong thing owns it. Meanwhile genuinely local interface state routed through a server cache makes trivial interactions asynchronous for no reason.
The decision. Split by ownership rather than by convenience. React Query holds anything Shopify is authoritative for; Jotai holds anything the browser is authoritative for.
Implementation. Cart reads and mutations go through React Query, so what renders is the cart Shopify returned, kept current against the platform rather than patched locally to look correct. Interface state sits in Jotai atoms that never carry commerce data. The boundary is the rule for deciding where anything new belongs: if Shopify can change it without the browser knowing, it is server state.
Outcome. The cart a customer sees is the cart Shopify will charge for, with no reconciliation step in between. When a mutation fails it fails with the platform's reason, instead of leaving a local copy that still looks fine.
A checkout you can brand but cannot rebuild
The problem. The storefront is fully custom right up until the customer reaches checkout, which is Shopify's, hosted by Shopify, and not a page in the application. The most sensitive moment of the purchase happens somewhere the codebase does not reach.
Why it was hard. Checkout Extensibility is deliberately a bounded surface. You configure and extend within what the platform exposes rather than hand-writing the page, and that constraint is the point — it is what keeps payment handling and its compliance burden on Shopify. The consequence is that brand continuity has to be achieved through the platform's own mechanisms, so the limits have to be understood before the experience is designed around them rather than after.
The decision. Accept the boundary and treat checkout as configuration rather than an obstacle to route around. Payment stays on Shopify.
Implementation. Checkout is customized through Checkout Extensibility, with its appearance driven by Shopify's GraphQL Branding API, so typography, color, and layout are set as data against the hosted checkout instead of injected into it. The storefront's responsibility ends at handing over a Shopify cart.
Outcome. The step from a custom storefront into the hosted checkout stays visually continuous, and payment behavior remains the platform's responsibility rather than the application's.
Outcome
Origins runs as a headless storefront: a custom Next.js front end over Shopify commerce, with a branded hosted checkout at the end of it.
Built solo, including the two places headless commerce usually goes wrong — keeping a single source of truth for the cart, and understanding which parts of checkout are genuinely open before designing around them.