← Back

Web App · E-Commerce

BODIEDBYTEMPL

Status

Live Production

Stack

Next.js 16Tailwind v4Shopify Storefront GraphQL API

[ THE THESIS ]

A real fashion storefront should look custom while commerce stays structurally correct.

BODIEDBYTEMPL is not WordPress, not a Shopify theme, and not a template. I built the storefront from scratch in Next.js, then connected Shopify underneath for products, inventory, checkout, payments, and orders. Variant IDs become the source of truth, so every color-size combination is tracked exactly and the frontend never touches payment data.

Live in production · processed real orders · Black Friday launch

Technical proof
15
variants per item
3 colors × 5 sizes
5
payment methods
Shop Pay · PayPal · GPay …
100%
custom frontend
not WordPress · not a theme
0
payment data touched
Shopify handles it

SECTION 01PRODUCT THESIS

Custom fashion storefront · trusted commerce backend · zero payment liability

The brand gets a real custom shopping experience, not a WordPress site or Shopify theme. Shopify stays behind the scenes for inventory, checkout, payments, orders, taxes, chargebacks, and PCI.

01

Custom frontend from scratch

The customer-facing storefront is a custom Next.js build: product pages, cart behavior, responsive media, and brand UI. Shopify is connected underneath through the Storefront GraphQL API.

02

Variant ID is the source of truth

Cart stores the variant ID, not the product ID. A specific size in a specific color is an atomic unit — double-counting a size is structurally impossible.

03

Payment data never enters the app

Shop Pay · PayPal · Google Pay · Venmo · UnionPay all process inside Shopify. The Next.js app has no card data, no PCI surface, no liability.

04

Git-push deploys

Vercel rebuilds on every push to main. Preview deploys per-PR. The brand iterates on copy and layout without asking an engineer.

SECTION 02ARCHITECTURE

Custom Next.js storefront · Shopify commerce backend · Vercel delivery

Customer browser · React 19

PDP

color/size selector

Cart

by variant ID

Brand UI

custom fashion layout

Checkout

handoff to Shopify

Next.js 16 · Tailwind v4 · Vercel edge · SSG + ISR

/products/[handle]

pre-rendered · revalidated

Fully custom UI

no theme · no template

Git push → deploy

automatic preview env

Shopify Plus Storefront API · GraphQL v2024-01

Read path

  • ▹ products / collections / variants
  • ▹ images / metafields
  • ▹ real-time inventory per variantId

Write path

  • ▹ cartCreate / cartLinesAdd
  • ▹ cart checkoutUrl → redirect
  • ▹ payment processed inside Shopify

SECTION 03ENGINEERING HIGHLIGHTS

Three decisions that made this survive Black Friday

CORRECTNESS

Cart keyed on variant ID, not product ID

15 combinations per item (3 colors × 5 sizes). Every line item references the unique variantId. Inventory checks query by variant. Selling a size out from under a customer is structurally impossible.

0 double-counted size bugs

COMMERCE SPLIT

Custom storefront, Shopify for the risky parts

I built the shopping experience, then connected Shopify for checkout, inventory, payments, and orders. All payment methods run inside Shopify, so the custom storefront never touches card data.

0 PCI surface · 5 payment methods

PERFORMANCE

SSG + ISR on Vercel edge

Every product page is pre-rendered at build time and served from the edge. Inventory + variant data revalidate on demand. A shopper on a bad connection still gets LCP under a second.

edge-cached · fast globally

SECTION 04CODE PROOF

Cart mutation · variant ID is the atomic unit

Verbatim from lib/shopify.ts.

lib/shopify.ts

typescript

// lib/shopify.ts — cartLinesAdd over the Storefront API
// Note: merchandiseId is the variant ID — not the product ID.

export async function addToCart(cartId: string, variantId: string, qty = 1) {
  const mutation = /* GraphQL */ `
    mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
      cartLinesAdd(cartId: $cartId, lines: $lines) {
        cart { id checkoutUrl totalQuantity
          lines(first: 50) { edges { node {
            id quantity
            merchandise { ... on ProductVariant {
              id title
              product { title }
              selectedOptions { name value }
            } }
          } } }
        }
        userErrors { field message }
      }
    }
  `;

  const res = await shopifyFetch({
    query: mutation,
    variables: {
      cartId,
      lines: [{ merchandiseId: variantId, quantity: qty }],
    },
  });

  if (res.userErrors?.length) throw new Error(res.userErrors[0].message);
  return res.cart;   // cart.checkoutUrl hands off to Shopify
}