Building an Ecommerce Shop with Sanity Part 1: Why We Are Leaving Shopify Behind

If you've ever managed a Shopify store, you know the feeling: you want to add a simple feature, and suddenly you're browsing the app store, comparing monthly subscription fees, and wondering why something so basic requires yet another third-party app. After years of wrestling with Shopify's limitations and watching our monthly app costs balloon, we decided to build something better.
The Shopify Problem
Don't get me wrong—Shopify is an incredible platform that has democratized e-commerce for millions of businesses. But as our needs grew more specific, we kept hitting the same walls:
The App Trap
Want to add order bumps in your cart? That's an app ($29/month). Need post-purchase upsells? Another app ($49/month). Custom product bundles? Yet another app ($39/month). Before we knew it, we were paying over $300/month just in app subscriptions for features that should be built into the platform—or at least easy to build ourselves.
Limited Flexibility
Shopify's theme system and Liquid templating gave us some control, but any time we wanted to do something beyond basic customization, we were fighting the system. Want a custom checkout flow? Sorry, that's locked down. Need a unique product page layout? Hope it fits within the theme constraints.
Performance Overhead
Every app you install adds JavaScript to your storefront. Some apps were loading 500KB+ of unoptimized code, slowing down our pages and hurting conversion rates. We had no control over how these third-party scripts loaded or performed.
Data Silos
Customer data in Shopify, email subscribers in Klaviyo, support tickets in Zendesk, analytics in Google Analytics—our data was scattered across a dozen platforms with no unified view. Simple questions like 'What's the lifetime value of customers who used this upsell offer?' required complicated integrations or manual data exports.
What We Really Wanted
Before jumping into development, we spent time documenting exactly what we needed. Not nice-to-haves, but must-haves that would make or break our business:
Simple Product Management
We needed a way to manage products that was intuitive for non-technical team members. No complicated variant structures or confusing metafields. Just straightforward product data: name, price, images, description, variants. But we also wanted the flexibility to add custom fields when needed—without installing an app or hiring a developer.
Easy In-Cart Order Bumps
Order bumps are conversion gold. 'Add this complementary product for 20% off' displayed right in the cart can boost average order value by 15-30%. We wanted this to be configurable through a simple interface—no code required. Marketing should be able to test different bump offers weekly without bothering engineering.
Post-Purchase 1-Click Upsells
The customer just bought from you—they're in buying mode and their payment info is already entered. Showing a relevant upsell offer on the thank you page with one-click acceptance is incredibly effective. Shopify apps for this cost $50-100/month and often add seconds to page load. We wanted it built in, fast, and flexible.
Easy to Manage Everything
One interface to rule them all. Products, orders, customers, page content, email templates—we wanted everything in one place with a clean, intuitive UI. No jumping between admin panels or logging into five different services to complete a simple workflow.
Modular Page Building
Every product is different. Some need video testimonials, others need before/after sliders, and some need detailed specification tables. We wanted a modular system where we could compose product pages from reusable components—heroes, feature lists, testimonial carousels, stats sections—without touching code.
Direct Stripe Integration
Shopify takes a percentage of every transaction, even when you use their Stripe integration. We wanted to work directly with Stripe, keep more of our revenue, and have full control over the payment flow. Plus, Stripe's APIs are developer-friendly and well-documented.
Blazing Fast Performance
Every 100ms of page load time costs conversions. We wanted server-side rendering, optimized images, minimal JavaScript, and sub-second page loads. No bloated third-party scripts. No render-blocking resources. Just fast.
Choosing the Stack
With our requirements clear, we evaluated several options. Building on WordPress with WooCommerce felt like moving backwards. Custom Laravel or Rails apps would take months to build and maintain. Then we discovered the combination that changed everything: Next.js 16, Sanity CMS v4, and Stripe.
Next.js 16 with App Router
Next.js 16 gave us server components, streaming, and automatic code splitting out of the box. The App Router architecture meant we could render product pages on the server, stream content as it became available, and deliver minimal JavaScript to the client. Performance by default.
React 19's new features meant we could build rich, interactive components without sacrificing performance. The cart updates instantly. Product images load progressively. The checkout flow is smooth and responsive. All while keeping bundle sizes small.
Sanity CMS v4
This is where it all clicked. Sanity isn't just a headless CMS—it's a structured content platform that treats everything as data. Products, pages, orders, customers, settings—all modeled in one place with a beautiful Studio interface that our whole team loves using.
The schema system is incredibly powerful. Want to add a custom field to products? Define it in the schema, and it's instantly available in the Studio with validation, help text, and conditional fields. No database migrations, no backend code changes.
And the best part? We embedded the Studio directly into our Next.js app at /studio. One deployment, one codebase, no separate CMS to maintain. Content editors access it through our domain, and it just works.
Stripe Payment Intents API
Stripe's Payment Intents API gave us everything we needed: secure payment processing, support for dozens of payment methods, strong authentication compliance, and detailed webhook events. When a payment succeeds, Stripe tells us. When it fails, Stripe tells us. When a customer disputes a charge, Stripe tells us.
We built our order flow around Stripe webhooks. Payment succeeds → create order in Sanity → send confirmation email. Charge refunded → update order status → notify customer. It's reliable, testable, and handles edge cases gracefully.
The Monorepo Architecture
We structured the project as an npm workspaces monorepo with everything in one repository. The frontend workspace contains the Next.js app, which includes both the customer-facing site and the embedded Sanity Studio.
This architecture eliminated deployment complexity. One git push deploys everything: the storefront, the API routes, and the CMS. No coordinating releases across multiple services.
Content Modeling: Documents, Objects, and Singletons
Sanity's schema system is built around three concepts that made organizing our content model intuitive:
Documents
These are top-level content types: products, pages, posts, orders, and customers. Each document gets its own entry in the Studio sidebar. Products have SKUs, prices, variants, and inventory. Orders have line items, totals, customer info, and status tracking. It's straightforward and mirrors how we think about the business.
Objects
Reusable components that live inside documents. Page modules (hero sections, testimonial carousels, stats displays), product modules (image galleries, feature comparisons, before/after sliders), and utility objects (shipping rules, discount conditions, email templates).
This is where the modular page building comes in. A product page might have a hero module at the top, followed by a feature grid, then a video testimonial section, then a FAQ accordion. Content editors drag and drop modules to compose the perfect layout for each product.
Singletons
Global settings that should only have one instance: site settings (logo, navigation, footer), cart settings (order bump offers, shipping thresholds), and email settings (sender name, default templates). These singletons give the team control over site-wide behavior without touching code.
Want to change the free shipping threshold? Update the cart settings singleton. Want to test a new order bump offer? Edit the cart settings, select a product, set the discount percentage, and publish. Live in seconds.
Custom Studio Plugins
Sanity Studio's plugin system let us extend functionality exactly where we needed it. We built two custom plugins that became essential to our daily operations:
Order Management Plugin
Orders come in through Stripe webhooks and are automatically created as documents in Sanity. But our support team needed a way to view, search, filter, and manage orders without learning GROQ queries or browsing through raw document lists.
The order management plugin provides a custom Studio view with filtering by status, search by customer name or order number, sorting by date or total, and detailed order pages showing line items, customer info, payment status, and fulfillment tracking. Everything our support team needs in one clean interface.
Email Templates Plugin
We use React Email to build transactional email templates—order confirmations, shipping notifications, password resets. The templates are React components, which means they're type-safe and can be tested like any other code.
The email templates plugin allows content editors to customize email copy, preview rendered templates with sample data, and see exactly what customers will receive—all without touching React code. Marketing can test new subject lines and calls-to-action independently.
Building the Features We Wanted
With our stack chosen and content model defined, we started building the features that drove us away from Shopify in the first place.
In-Cart Order Bumps
We added an order bumps array to the cart settings singleton. Each order bump has a trigger condition (cart total threshold, specific products in cart), a product to offer, discount percentage, and custom messaging. When someone views their cart, we evaluate the conditions server-side and show the relevant offer.
The whole thing is configurable through the Studio interface. Marketing runs experiments weekly, testing different products, discount levels, and copy. No developer intervention required. Conversion lift has been significant.
Post-Purchase Upsells
After payment confirmation, we redirect customers to a thank you page that can display a one-click upsell offer. The offer is based on what they just purchased—bought the basic package? Here's the premium upgrade for 30% off.
Because we control the entire payment flow through Stripe, accepting the upsell is truly one click. We create a new Payment Intent for the upsell amount using their saved payment method, charge immediately, and update the order. The customer never re-enters their payment info. Friction eliminated.
These upsell offers are also configured through Sanity. Product associations, discount rules, offer copy—all managed in the Studio with real-time previews.
Development Workflow
One of the smartest decisions we made was automating schema extraction and TypeScript type generation. Before the dev server starts or production build runs, we automatically extract the Sanity schema to schema.json and generate TypeScript types into sanity.types.ts.
This means our TypeScript code always has accurate, up-to-date types that match our content model. When we add a field to the product schema, TypeScript immediately knows about it. When we change a field from optional to required, the compiler tells us everywhere that needs updating. It's caught countless bugs before they reached production.
The Results
Six months in, here's where we stand compared to our Shopify days:
- Monthly costs down 60% (no more $300+ in app subscriptions)
- Page load times down 70% (from 4.2s to 1.3s average)
- Conversion rate up 23% (faster pages + better UX)
- Average order value up 31% (order bumps + post-purchase upsells)
- Content team moves faster (no waiting on developers for simple changes)
- Support team happier (unified order management interface)
But the biggest win? We control everything. Need a new feature? We build it. Want to test a new conversion optimization? We implement it. No more waiting for app developers to add functionality or hoping Shopify will prioritize our feature request.
What's Next
This is just the foundation. In Part 2, we'll dive deep into the Stripe integration—how we handle Payment Intents, webhook events, idempotency, order creation, and the state management that ties it all together with Zustand. We'll cover the gotchas we encountered (there were many) and exactly how we solved them.
In Part 3, we'll explore the modular page building system in detail—how we built flexible, composable modules that content creators love and how we optimized them for performance.
If you're frustrated with Shopify's limitations and monthly costs, or just curious about building your own e-commerce platform, stay tuned. And feel free to reach out with questions—we're happy to share what we've learned.

