How it's built
Case study — R Commerce
The short version
R Commerce is a portfolio storefront with twenty fictional products, a working cart, real accounts and a Shopify checkout running in test mode. Next.js on Vercel renders the site. Shopify handles checkout, Supabase handles accounts, and an AI Concierge answers questions inside the account page.
Architecture
The site runs in three layers. The browser holds the cart and talks to Shopify, the Concierge and analytics directly. Next.js on Vercel renders the pages and runs every action that needs the signed-in session. Supabase, the Shopify Admin API and Resend sit behind the server, so their keys never reach the browser.
Browser
Cart
Held in the browser
Shopify Storefront API
Stock and checkout
Concierge backend
AI chat
GTM and GA4
Consent-aware analytics
Next.js on Vercel
Proxy
Session and consent mode
Pages
Pre-rendered products
Server Actions
Account reads and writes
Services
Supabase
Accounts, saves and carts
Shopify Admin API
Order history
Resend
Newsletter sign-ups
Why headless
The storefront is a design piece, with page transitions, a WebGL product orb and scroll-driven reveals. A Shopify theme would limit all of that. Going headless keeps the design free and leaves payment to Shopify's hosted checkout. The cart lives on the site, and a stock check that gets no answer never blocks anything, so browsing and the cart keep working even when Shopify is slow.
See the orb in the lookbookAccounts and the Concierge
Accounts
Email and password sign-in with email verification, a profile, saved items and a saved cart. Row-level security limits every user to their own rows.
Server Actions
Everything that needs the signed-in session runs on the server. One round-trip per navigation returns the sign-in state, the saved cart and the wishlist.
Concierge
The account page includes an AI assistant. Its prompt is built from the catalogue, the FAQ and the shopper's own account, orders and cart, and it is instructed to answer only from those facts. A separate backend holds the model key.
Create an account to try it
Stock and orders
Stock checks
The product page and the cart check every line in one batched GraphQL query. Sold-out sizes are blocked and quantities are capped at what is left. An unknown answer never blocks a purchase.
Browse the shopGuest to account
Signing in merges the guest cart into the saved one, and the lines on the device win on duplicates.
Checkout hand-off
The Shopify cart carries the shopper's GA4 client and session IDs, so the purchase lands in the same analytics session. If Shopify refuses those extras, checkout retries with the bare lines, so attribution never costs a sale.
After the purchase
A Shopify cart that becomes an order stops existing. When the shopper returns, the saved cart id is looked up, and if it is gone, the lines that went to checkout are removed from the device and the account.
Signed order history
Signed-in checkouts attach the user ID and an HMAC signature, which Shopify copies onto the order. The account page reads orders with a server-only token and lists only those whose signature verifies.
Analytics and consent
Region-aware consent
On the first visit, the proxy reads Vercel's geo headers and sets the consent mode. Visitors in the EU, EEA, UK, Switzerland and Quebec opt in. Elsewhere, analytics runs until the visitor declines. An unknown location is treated as opt-in.
Nothing before a choice
In opt-in regions, Google Tag Manager is not requested until the visitor accepts. Declining clears the GA cookies. Tags load only on the live domain, so local and preview builds send nothing.
Read the privacy noticeThe funnel
Ecommerce events run from list impressions to begin_checkout, and Shopify's checkout pixel adds payment and purchase to the same property. Signed-in hits carry the Supabase user ID, and email addresses are never sent to analytics.
Performance
Static pages
All twenty product pages are pre-rendered at build and served from Vercel's CDN, so they open without a server render.
Pre-sized images
A script writes WebP copies of the large images at up to four widths, from 640 to 2400 pixels. A custom next/image loader picks the smallest copy that covers its slot, and each image's sizes value follows the layout at every breakpoint.
Open the lookbookWarm-up
During the intro, the site's images load in idle-time batches with the same srcset each page will use, so later pages open from the cache.
Scoped proxy
The auth proxy skips static files, images and the newsletter endpoint, so Supabase is called only where the session matters.
Touch scrolling
On phones and tablets that support it, the pinned image reveal on the home page runs as a CSS scroll-driven animation, so it stays smooth while the page scrolls.
See it on the home page
Accessibility
Keyboard
The menu, cart, consent card and popup all close on Escape. The closed menu and cart are inert, so Tab cannot reach them.
Skip link and focus
A skip link jumps past the header, and every link and button shows the same accent focus ring on keyboard focus.
Letter reveals
Text that reveals letter by letter hides the pieces from screen readers and exposes one plain copy instead.
FAQ answers
Collapsed answers are inert, so keyboard and screen-reader users reach only the answer that is open.
Open the FAQ
Stack
- Next.js 16
- React 19
- GSAP
- Lenis
- Three.js
- Zustand
- Supabase
- Shopify
- Resend
- GA4
- Vercel
