Skip to content
TT

Senior Shopify & Headless Commerce Engineer

Nine years of Shopify, mostly at the difficult end of it.

Headless storefronts at 300,000 products across two regions. A WooCommerce store held at peak throughput without checkout degradation. An app installed natively on Shopify, Magento and BigCommerce from one core. If you are replatforming, going headless, or debugging a checkout hand-off that only fails in production, this is the work.

  • Senior Shopify Developer
  • Shopify Plus Consultant
  • Headless Commerce Engineer
  • Solutions Engineer
Shopify expertise

What I actually mean when I say I know Shopify.

Not a badge list. Each of these is a decision I have had to make on a production store, and the reasoning behind it.

Headless Shopify

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.

Storefront API

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.

Cursor pagination that stays cheap at catalogue scale
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 }
        }
      }
    }
  }
}

Admin API

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.

Respecting the cost-based rate limit instead of hoping
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;
}

Buy Button SDK & checkout

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.

Custom Pixel

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.

Custom Pixel: forwarding a real purchase event to GA4
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,
    })),
  });
});

GA4 & GTM

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.

Performance

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.

Conversion optimisation

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.

Cypress E2E

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.

Guarding the checkout hand-off boundary
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");
  });
});