Solving Shopify's Dev Store Checkout Problem with a Dual-Mode Architecture
TL;DR: Shopify dev stores are password-protected, which blocks checkout testing entirely. I built a starter kit with Stripe fallback for development, native Shopify checkout for production. Looking for architecture feedback before I keep building.
The Problem Nobody Talks About
If you've built a headless Shopify store, you've probably hit this wall:
You spin up a free development store. You connect the Storefront API. Products load beautifully. You build your cart. Then you try to test checkout and... password page.
Shopify dev stores are permanently password-protected, and that protection extends to the hosted checkout. When your headless frontend redirects to checkoutUrl, visitors (including you) hit the password wall instead of completing a purchase.
This means you can't:
- Demo a complete purchase flow to a client
- Test your order creation webhooks
- Verify fulfillment integrations work
- Show investors/stakeholders a working prototype
Hydrogen has the same limitation — Shopify's own docs acknowledge it: "Online store password protection prevents Hydrogen checkouts."
The Architecture
The solution is dual-mode checkout:
Development: Cart → Stripe Elements → Payment Intent → Admin API → Order
Production: Cart → Shopify Checkout URL → Native Hosted Checkout → Order
One environment variable switches between them:
# Development (works on free dev stores)
VITE_CHECKOUT_MODE=stripe
# Production (native Shopify checkout)
VITE_CHECKOUT_MODE=shopify
How Stripe Mode Works
Instead of redirecting to Shopify's hosted checkout, the Stripe path:
- Collects payment via Stripe Elements (full UI control)
- Creates a PaymentIntent server-side
- On success, creates a draft order via Admin API
- Completes the draft order (marks as paid)
The result is a real order in Shopify Admin — inventory decrements, webhooks fire, fulfillment workflows trigger. Indistinguishable from a checkout-created order.
Why Not Just Use Stripe Forever?
You could, but Shopify's native checkout handles:
- Shop Pay, Apple Pay, Google Pay
- Shopify's fraud analysis
- Automatic tax calculation
- Their checkout conversion optimizations
For production stores with a paid Shopify plan, native checkout is the right choice. The Stripe path exists purely to unblock development and demos.
What I Actually Built
Stack: React 18, TypeScript (strict), React Router 7, Tailwind, Vite
Features:
- Shopify Storefront API integration (products, collections, variants)
- SSR-safe cart persistence (no hydration errors)
- Stripe Elements checkout with Admin API order creation
- Cloudinary CDN for image optimization
- 347 tests, 89% coverage
Live demo: ecommerce-react-shopify.vercel.app
Test card: 4242 4242 4242 4242 (any future expiry, any CVC)
Where I Want Feedback
I'm self-taught and working solo on this, so I'd genuinely appreciate experienced eyes on:
1. Cart Context Pattern
I'm using useSyncExternalStore for SSR-safe localStorage persistence. It works, but I'm not sure if it's overengineered. The implementation is in src/lib/createPersistedStore.ts.
2. Checkout Flow Security
Payment verification happens server-side before order creation, but are there edge cases I'm missing? The logic is in app/routes/api.create-order.ts.
3. Project Structure
Does the file organization make sense, or is anything non-obvious?
Links
- Repo: github.com/nathanmcmullendev/ecommerce-react
- Setup Guide: docs/guides/SETUP-CHECKLIST.md
- Checkout Docs: docs/guides/CHECKOUT-MODES.md
Roast it or tell me it's fine. Either helps.
Top comments (0)