DEV Community

Nathan M
Nathan M

Posted on • Edited on

Shopify Dev Starter Kit - Store Checkout with a Dual-Mode Architecture

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
Enter fullscreen mode Exit fullscreen mode

One environment variable switches between them:

# Development (works on free dev stores)
VITE_CHECKOUT_MODE=stripe

# Production (native Shopify checkout)
VITE_CHECKOUT_MODE=shopify
Enter fullscreen mode Exit fullscreen mode

How Stripe Mode Works

Instead of redirecting to Shopify's hosted checkout, the Stripe path:

  1. Collects payment via Stripe Elements (full UI control)
  2. Creates a PaymentIntent server-side
  3. On success, creates a draft order via Admin API
  4. 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


Roast it or tell me it's fine. Either helps.

Top comments (0)