Headless Commerce December 10, 2025 14 min read

Headless Shopify in 2026: The Stack That Actually Works

Hydrogen locks you to Shopify hosting. Gatsby is dead. Remix has a tiny ecosystem. After building 20+ headless Shopify storefronts, here is the stack that ships fast, scales cleanly, and costs almost nothing to run.

Tyler Colby · Founder, Colby's Data Movers

The State of Headless Shopify Frameworks

Every six months, someone writes a blog post declaring that headless Shopify has finally arrived. The tooling is mature. The ecosystem is thriving. Just pick a framework and go.

That advice gets people into trouble. The headless Shopify landscape in late 2025 is littered with abandoned projects, deprecated frameworks, and vendor lock-in disguised as developer experience. If you pick the wrong stack today, you will be rewriting in 18 months.

I have built over 20 headless Shopify storefronts in the last two years. For brands selling products from $200 to $18,000. I have tried every major approach. Most of them are bad. One combination works reliably, ships fast, and does not lock you into anything. That is what this post is about.

Let me walk through what is out there, why most of it fails, and what you should actually use.

Hydrogen: Shopify's Official Answer

Hydrogen is Shopify's React framework for building custom storefronts. It launched in 2022, got a major rewrite in 2023 built on top of Remix, and has been steadily improving since. The developer experience is genuinely good. The Storefront API integration is tight. The components library saves real time.

Here is the problem: Hydrogen runs on Oxygen. Oxygen is Shopify's hosting platform. You cannot deploy Hydrogen to Vercel, Netlify, Cloudflare, or AWS without significant effort and lost functionality. Shopify designed it this way on purpose. They want you on their infrastructure.

That means your hosting bill, your deployment pipeline, your edge caching, your custom domains, your SSL certificates, your CDN configuration, and your CI/CD are all controlled by Shopify. You traded template lock-in for infrastructure lock-in. That is not headless. That is a different shape of the same cage.

Hydrogen deployment options:
- Oxygen (Shopify hosting)     Full support, full lock-in
- Vercel/Netlify               Partial support, missing features
- Self-hosted Node             You maintain everything
- Cloudflare Workers           Experimental, community-driven

The "headless" framework that only works well
on one host is not headless. It is a managed service.

There are also practical concerns. Oxygen's pricing is opaque at scale. Their edge network is smaller than Cloudflare's or Vercel's. Custom middleware and edge functions are limited compared to what Vercel or Cloudflare offer natively. And if Shopify decides to change Oxygen's pricing, deprecate features, or sunset the platform entirely, you have no fallback without a rewrite.

Hydrogen is good software built on a bad premise. If you are comfortable with full Shopify dependency for your entire frontend infrastructure, it works. For everyone else, it is a trap.

Gatsby: A Post-Mortem

Gatsby was the original headless Shopify darling. The gatsby-source-shopify plugin made it trivially easy to pull products into a static site. The GraphQL data layer was elegant. Build times were acceptable for stores under 500 products. The ecosystem was massive.

Gatsby is effectively dead.

Netlify acquired Gatsby Inc. in February 2023. Development slowed to a crawl. The core team scattered. The plugin ecosystem stopped receiving updates. gatsby-source-shopify has not had a meaningful update in over a year. Build times for large catalogs remain painful. The incremental builds feature that was supposed to fix this never fully materialized.

If you have an existing Gatsby + Shopify site that is working, you can maintain it. But starting a new project on Gatsby in 2026 would be like buying a new car from a manufacturer that just closed its factory. The parts will get harder to find, the mechanics who know the system will move on, and eventually you will be forced to migrate anyway.

Do not start new projects on Gatsby. Plan your migration off it if you are currently running one.

Remix: Good Framework, Small Shopify Ecosystem

Remix is a legitimately excellent web framework. The nested routing model is clean. The data loading patterns are intuitive. Server-side rendering is a first-class concern. Ryan Florence and Michael Jackson built something genuinely good.

The problem is not Remix. The problem is the Shopify ecosystem around Remix. Beyond Hydrogen itself (which is built on Remix but locked to Oxygen), the community of Remix + Shopify developers is small. Tutorials are sparse. Third-party packages for common Shopify patterns are limited. When you hit a problem at 2 AM before a launch, Stack Overflow will have three results instead of three hundred.

Remix also has a smaller deployment story than Next.js. It works on Vercel, Netlify, Cloudflare, and Fly.io. But the adapters are less mature. Edge rendering support varies by platform. The ecosystem of middleware, authentication libraries, and utility packages is a fraction of what Next.js offers.

If you are a Remix expert and you prefer its patterns, you can absolutely build a great Shopify storefront with it. But for most teams, the ecosystem gap creates real friction. You will spend more time solving problems that Next.js has already solved at scale.

The Stack That Actually Works

After two years of building and shipping headless Shopify stores, this is the stack I reach for every time. Not because it is trendy. Because it works, it ships fast, and it does not lock you into anything.

The Production Stack:
- Next.js 16          (App Router, RSC, ISR)
- Shopify JSON API    (Public, zero tokens)
- Tailwind CSS 4      (Utility-first styling)
- Vercel              (Deploy target, edge CDN)
- localStorage        (Client-side cart state)
- Shopify Checkout    (Permalink-based redirect)

Every piece of this stack is independently replaceable. You can swap Vercel for Cloudflare Pages. You can swap Tailwind for vanilla CSS. You can swap the JSON API for the Storefront API if you need more data. Nothing is welded together. Nothing requires a vendor-specific runtime. Nothing locks you in.

Let me walk through each piece and explain why it is here.

Next.js 16: The Obvious Choice

Next.js has the largest React ecosystem on the planet. When you Google a problem, there are answers. When you need a library, it exists. When you hire a developer, they have used it before.

Next.js 16 ships with the App Router as the stable default. React Server Components are production-ready. The caching system is mature. Partial prerendering lets you mix static and dynamic content on the same page without sacrificing performance.

For Shopify storefronts specifically, Next.js gives you three things that matter:

Static Site Generation with Incremental Static Regeneration (ISR). You generate every product page at build time. The pages are static HTML served from the edge. When a product changes in Shopify, ISR regenerates just that page in the background. Visitors always get a cached static page. The site is fast by default, not fast by effort.

// app/products/[handle]/page.tsx

export async function generateStaticParams() {
  const products = await fetch(
    'https://your-store.myshopify.com/products.json?limit=250'
  ).then(r => r.json());

  return products.products.map((p) => ({
    handle: p.handle,
  }));
}

export const revalidate = 300; // Regenerate every 5 minutes

export default async function ProductPage({ params }) {
  const { handle } = await params;
  const product = await fetch(
    `https://your-store.myshopify.com/products/${handle}.json`
  ).then(r => r.json());

  return <ProductDetail product={product.product} />;
}

That is the entire data fetching layer for a product page. No GraphQL. No SDK. No API token. A single fetch call to a public URL. The page is generated at build time, cached at the edge, and revalidated every five minutes. Your Lighthouse score will be above 95 without trying.

React Server Components for zero-JS product pages. Product detail pages do not need client-side JavaScript for their core content. The product title, description, images, price, and specifications are all static data. With RSC, that content renders on the server and ships as pure HTML. No JavaScript bundle for the product content. The only JS that loads is the interactive cart button and any client-side components you explicitly mark with "use client".

Edge middleware for geolocation and personalization. Next.js middleware runs at the edge before the page loads. You can use it for currency detection based on geolocation, A/B testing, redirect rules, and bot detection. All without adding latency to the page load because it runs at the CDN layer.

The Shopify Public JSON API: Zero Tokens Required

This is the most underutilized feature in the Shopify ecosystem. Every Shopify store exposes a public JSON API. No API key. No access token. No OAuth flow. No Storefront API setup. No app installation.

Append .json to any Shopify URL and you get structured data back.

Public JSON endpoints (no authentication required):

Products:
  /products.json                    All products (paginated)
  /products/[handle].json           Single product with variants
  /collections.json                 All collections
  /collections/[handle].json        Collection metadata
  /collections/[handle]/products.json  Products in collection

Search:
  /search/suggest.json?q=query      Search suggestions
  /search.json?q=query              Search results

Pages:
  /pages/[handle].json              Static page content

Blog:
  /blogs/[handle].json              Blog metadata
  /blogs/[handle]/articles.json     Blog articles

Pagination:
  /products.json?page=2&limit=50    Standard pagination

The product JSON includes everything you need for a storefront: title, description (HTML), price, compare-at price, images with alt text, variants with inventory status, tags, vendor, product type, and metafields that are configured as visible.

There are limitations. The public JSON API does not support filtering by metafield, does not expose draft products, does not support writing operations, and paginates differently than the Storefront API. For 90% of storefronts, none of that matters. You are reading product data and displaying it. The public JSON API does exactly that.

The zero-token aspect is not a gimmick. It is architecturally significant. It means your build pipeline has no secrets. Your CI/CD does not need environment variables for Shopify access. Your preview deployments work without configuration. Your local development environment works out of the box. There is nothing to rotate, nothing to expire, nothing to leak.

// lib/shopify.ts

const STORE = 'your-store.myshopify.com';

export async function getProducts(limit = 250) {
  const res = await fetch(
    `https://${STORE}/products.json?limit=${limit}`,
    { next: { revalidate: 300 } }
  );
  const data = await res.json();
  return data.products;
}

export async function getProduct(handle: string) {
  const res = await fetch(
    `https://${STORE}/products/${handle}.json`,
    { next: { revalidate: 300 } }
  );
  const data = await res.json();
  return data.product;
}

export async function getCollection(handle: string) {
  const res = await fetch(
    `https://${STORE}/collections/${handle}/products.json?limit=250`,
    { next: { revalidate: 300 } }
  );
  const data = await res.json();
  return data.products;
}

// That is the entire Shopify client. No SDK. No dependencies.

Compare that to the Storefront API setup: create a custom app, configure API scopes, generate a Storefront access token, install a GraphQL client, write typed queries, handle pagination cursors, manage token rotation. For a read-only storefront, all of that is unnecessary overhead.

Tailwind CSS 4: Styling That Scales

Tailwind CSS is here because it solves the only styling problem that actually matters at the storefront level: consistency across a large number of pages that share visual patterns but differ in content.

A headless Shopify store typically has 8-15 page templates: product detail, collection grid, homepage, about, contact, cart, search results, blog index, blog post, policy pages, and custom landing pages. Each template needs to be visually consistent but structurally different. Tailwind's utility classes make this trivial. You compose styles inline. You extract components when patterns repeat. You never fight specificity wars.

Tailwind CSS 4 compiles to standard CSS at build time. The output is a single CSS file containing only the utilities you actually used. For a typical storefront, that is 15-25KB gzipped. Compare that to a theme CSS file from a Shopify template that ships 200-400KB of styles, most of which are unused on any given page.

// A product card component with Tailwind

function ProductCard({ product }) {
  const price = parseFloat(product.variants[0].price);
  const compareAt = parseFloat(product.variants[0].compare_at_price);
  const onSale = compareAt && compareAt > price;

  return (
    <a href={`/products/${product.handle}`}
       className="group block rounded-xl border border-zinc-800
                  bg-zinc-900 p-4 transition hover:border-cyan-500
                  hover:shadow-lg hover:shadow-cyan-500/10">
      <div className="aspect-square overflow-hidden rounded-lg">
        <img
          src={product.images[0]?.src}
          alt={product.images[0]?.alt || product.title}
          className="h-full w-full object-cover transition
                     group-hover:scale-105"
          loading="lazy"
          width={600}
          height={600}
        />
      </div>
      <h3 className="mt-3 text-sm font-medium text-zinc-100">
        {product.title}
      </h3>
      <div className="mt-1 flex items-center gap-2">
        <span className="text-sm font-semibold text-white">
          ${price.toFixed(2)}
        </span>
        {onSale && (
          <span className="text-xs text-zinc-500 line-through">
            ${compareAt.toFixed(2)}
          </span>
        )}
      </div>
    </a>
  );
}

No CSS files to manage. No BEM naming conventions to enforce. No style collisions between pages. The component is self-contained. Copy it between projects and it works identically.

Client-Side Cart via localStorage

This is where most headless Shopify tutorials overcomplicate things. They tell you to use the Storefront API's cart mutations, the Shopify Buy SDK, or a third-party cart service. All of those require API tokens, add JavaScript weight, and introduce failure points.

You do not need any of that. A shopping cart is a list of variant IDs and quantities. Store it in localStorage. Render it client-side. When the customer is ready to check out, redirect them to Shopify's checkout with the cart contents encoded in the URL.

// lib/cart.ts

export interface CartItem {
  variantId: number;
  quantity: number;
  title: string;
  price: string;
  image: string;
  handle: string;
}

export function getCart(): CartItem[] {
  if (typeof window === 'undefined') return [];
  const raw = localStorage.getItem('cart');
  return raw ? JSON.parse(raw) : [];
}

export function addToCart(item: CartItem) {
  const cart = getCart();
  const existing = cart.find(i => i.variantId === item.variantId);
  if (existing) {
    existing.quantity += item.quantity;
  } else {
    cart.push(item);
  }
  localStorage.setItem('cart', JSON.stringify(cart));
  window.dispatchEvent(new Event('cart-updated'));
}

export function removeFromCart(variantId: number) {
  const cart = getCart().filter(i => i.variantId !== variantId);
  localStorage.setItem('cart', JSON.stringify(cart));
  window.dispatchEvent(new Event('cart-updated'));
}

export function getCartCount(): number {
  return getCart().reduce((sum, item) => sum + item.quantity, 0);
}

export function clearCart() {
  localStorage.removeItem('cart');
  window.dispatchEvent(new Event('cart-updated'));
}

The cart lives entirely in the browser. No server calls to add or remove items. No latency. No loading spinners. No API rate limits. The cart drawer opens instantly because there is no network request.

The custom event pattern (window.dispatchEvent(new Event('cart-updated'))) lets any component on the page react to cart changes. The header cart icon listens for the event and updates the count. The cart drawer listens and re-renders. No state management library required. No context providers. No Redux. Just browser events.

The only limitation is that the cart does not persist across devices. A customer who adds items on their phone will not see them on their desktop. For high-ticket products where the purchase cycle spans multiple sessions, this matters less than you think. The customer bookmarks the product page, not the cart. They return to the product and re-add. The research-heavy purchase flow makes cross-device cart sync a nice-to-have, not a requirement.

Shopify Checkout Permalinks: The Redirect Pattern

When the customer clicks "Checkout," you need to get them into Shopify's checkout flow with their cart contents. Shopify's checkout is excellent. PCI compliant. Supports Shop Pay, Apple Pay, Google Pay, and every other payment method. You do not want to rebuild it. You want to redirect to it.

Shopify supports checkout permalinks. You construct a URL with variant IDs and quantities, and Shopify creates the checkout session automatically.

// lib/checkout.ts

const STORE_DOMAIN = 'your-store.myshopify.com';

export function buildCheckoutUrl(cart: CartItem[]): string {
  // Format: /cart/variantId:quantity,variantId:quantity
  const lineItems = cart
    .map(item => `${item.variantId}:${item.quantity}`)
    .join(',');

  return `https://${STORE_DOMAIN}/cart/${lineItems}`;
}

// Usage in a checkout button:
function CheckoutButton() {
  const handleCheckout = () => {
    const cart = getCart();
    if (cart.length === 0) return;
    const url = buildCheckoutUrl(cart);
    clearCart();
    window.location.href = url;
  };

  return (
    <button onClick={handleCheckout}
            className="w-full rounded-lg bg-cyan-500 py-3
                       font-semibold text-zinc-900 transition
                       hover:bg-cyan-400">
      Checkout
    </button>
  );
}

The customer clicks Checkout. The browser redirects to Shopify. Shopify creates a cart with the specified variants and quantities. The customer sees the standard Shopify checkout. They pay. The order appears in the Shopify admin. Done.

No Storefront API. No cart creation mutations. No checkout session tokens. No webhook handling for cart state. The permalink approach eliminates an entire category of complexity.

You can also append discount codes, notes, and attributes to the URL:

// With discount code
https://store.myshopify.com/cart/123456:1,789012:2?discount=SUMMER20

// With cart note
https://store.myshopify.com/cart/123456:1?note=Gift+wrapping+please

// With cart attributes
https://store.myshopify.com/cart/123456:1?attributes[ref]=homepage

The attributes parameter is particularly useful for attribution. You can track which page or campaign drove the checkout without any analytics setup on the Shopify side.

Vercel: The Deployment Target

Vercel is the default deployment target because it is built by the Next.js team and provides the best integration. But this is the most replaceable part of the stack. You can deploy to Cloudflare Pages, Netlify, or any platform that supports Node.js.

What Vercel gives you specifically:

  • ISR support. When a page needs revalidation, Vercel handles it at the edge. The stale page is served immediately while the new page generates in the background. This is the core mechanism that keeps product pages fast and fresh.
  • Edge middleware. Geolocation, redirects, A/B tests, and bot detection run at the CDN layer before the page loads. No cold start. No server roundtrip.
  • Preview deployments. Every pull request gets a unique URL. Since there are no API tokens or secrets required for the Shopify JSON API, preview deployments work with zero configuration. Push a branch. Get a live preview. Share it with the client.
  • Image optimization. Vercel's image CDN handles resizing, format conversion (WebP/AVIF), and caching for product images. You point to the Shopify CDN image URL and Vercel optimizes it on the fly.

The free tier handles most small-to-medium storefronts. The Pro plan at $20/month handles everything else. Compared to Shopify Plus at $2,000+/month for advanced storefront features, the hosting cost is negligible.

The Full Architecture

Here is how all the pieces connect:

Build Time (every deploy + ISR revalidation):
┌──────────────┐     GET /products.json     ┌──────────────┐
│   Next.js    │ ────────────────────────── >│   Shopify    │
│  Build/ISR   │< ──────────────────────────  │  Public API  │
└──────┬───────┘     Product JSON data       └──────────────┘
       │
       │ Static HTML + minimal JS
       ▼
┌──────────────┐
│ Vercel Edge  │  Cached static pages
│    CDN       │  95+ Lighthouse score
└──────┬───────┘
       │
       │ Fast static response
       ▼
┌──────────────┐

Runtime (customer browsing):
┌──────────────┐
│   Browser    │  Product pages: static HTML from CDN
│              │  Cart: localStorage (no network)
│              │  Search: client-side against prefetched data
└──────┬───────┘
       │
       │ Click "Checkout"
       ▼
┌──────────────┐
│   Shopify    │  /cart/variantId:qty,variantId:qty
│  Checkout    │  Standard Shopify checkout flow
│              │  Shop Pay, Apple Pay, etc.
└──────────────┘

During browsing, the only network requests are for images (served from CDN) and any client-side search or filtering. Product data is already in the static HTML. The cart is in localStorage. There is no backend to call, no API to hit, no server to keep warm.

At checkout time, the customer redirects to Shopify. From Shopify's perspective, it looks like a customer who filled a cart on the regular storefront. Orders, fulfillment, email notifications, and everything else in the Shopify admin works exactly as before.

What About Search?

Search is the one area where the public JSON API has real limitations. The /search/suggest.json endpoint works for basic autocomplete, but it is rate-limited and not customizable.

For stores with fewer than 500 products, I prefetch all products at build time and run search client-side using a library like Fuse.js. The entire product catalog fits in a JSON file under 200KB. Search is instant because it runs in the browser against local data.

// For small catalogs: client-side search with Fuse.js
import Fuse from 'fuse.js';

const fuse = new Fuse(allProducts, {
  keys: ['title', 'body_html', 'vendor', 'product_type', 'tags'],
  threshold: 0.3,
  includeScore: true,
});

function search(query: string) {
  return fuse.search(query).map(result => result.item);
}

For stores with 500+ products, you need a proper search service. Algolia or Typesense. Both have Shopify integrations that sync your product catalog automatically. The search index lives outside Shopify and outside your frontend. It is another independent, replaceable piece.

Do not use Shopify's native search for a headless storefront. It is designed for Liquid templates and returns HTML, not structured data.

Handling Inventory and Pricing

The public JSON API returns current inventory policy (continue selling or deny when out of stock) but does not return exact inventory counts. It returns whether a variant is "available" or not. For most storefronts, that boolean is sufficient.

If you need exact inventory counts or real-time pricing that changes frequently, you have two options:

  1. Shorter ISR intervals. Set revalidate to 60 seconds instead of 300. Pages regenerate more often. Inventory and pricing lag by at most one minute. For most stores, this is accurate enough.
  2. Client-side variant fetch. When the customer selects a variant on the product page, make a client-side fetch to the JSON API for that specific product. This gets the latest price and availability without waiting for ISR. The initial page load is still static and fast. The variant data refreshes when the customer interacts.
// Client-side variant refresh on interaction
'use client';

import { useState, useEffect } from 'react';

function VariantSelector({ handle, variants: initialVariants }) {
  const [variants, setVariants] = useState(initialVariants);
  const [selected, setSelected] = useState(initialVariants[0]);

  // Refresh variant data when component mounts
  useEffect(() => {
    fetch(`https://store.myshopify.com/products/${handle}.json`)
      .then(r => r.json())
      .then(data => {
        setVariants(data.product.variants);
        // Keep the same variant selected
        const current = data.product.variants.find(
          v => v.id === selected.id
        );
        if (current) setSelected(current);
      });
  }, [handle]);

  return (
    <div>
      {variants.map(v => (
        <button
          key={v.id}
          onClick={() => setSelected(v)}
          className={selected.id === v.id
            ? 'border-cyan-500 bg-cyan-500/10'
            : 'border-zinc-700'}
        >
          {v.title} - ${parseFloat(v.price).toFixed(2)}
          {!v.available && ' (Sold Out)'}
        </button>
      ))}
    </div>
  );
}

The page loads instantly with static variant data from build time. The client-side fetch updates prices and availability in the background. The customer never sees a loading state because the static data is already rendered.

Performance Numbers

Here are real Lighthouse scores from five headless Shopify stores running this exact stack, compared to their previous Shopify template implementations:

Store A (Outdoor equipment, 340 products):
  Template (Dawn):     Performance 34, FCP 3.2s, LCP 5.1s
  Headless (Next.js):  Performance 97, FCP 0.6s, LCP 1.1s

Store B (Luxury home goods, 180 products):
  Template (Prestige): Performance 28, FCP 3.8s, LCP 6.2s
  Headless (Next.js):  Performance 99, FCP 0.4s, LCP 0.8s

Store C (Industrial safety, 890 products):
  Template (Dawn):     Performance 41, FCP 2.9s, LCP 4.8s
  Headless (Next.js):  Performance 94, FCP 0.7s, LCP 1.3s

Store D (Custom accessories, 75 products):
  Template (Minimal):  Performance 45, FCP 2.6s, LCP 4.1s
  Headless (Next.js):  Performance 100, FCP 0.3s, LCP 0.5s

Store E (Premium tools, 520 products):
  Template (Dawn):     Performance 37, FCP 3.4s, LCP 5.5s
  Headless (Next.js):  Performance 96, FCP 0.5s, LCP 1.0s

Average improvement: +58 Lighthouse points, -3.5s LCP

These are not cherry-picked. These are consecutive production builds using this stack. The performance improvement is not marginal. It is transformative. A 3.5-second improvement in Largest Contentful Paint directly correlates with conversion rate improvements. Google has published the data. Every 100ms of improvement matters.

What This Stack Does Not Do

Transparency matters. Here are the things this stack does not handle out of the box:

  • Customer accounts. The public JSON API does not expose customer data. If you need customer login, order history, or saved addresses, you need the Storefront API or the Customer Account API. This requires an access token.
  • Multipass SSO. If you use Shopify Multipass for single sign-on with an external identity provider, you need a server-side component and API credentials.
  • Draft products. The public JSON API only returns published products. If you need to preview unpublished products, you need the Admin API or Storefront API.
  • B2B pricing. Customer-specific or quantity-based pricing requires authenticated API access.
  • Subscriptions. Subscription products require the Storefront API for selling plan data.

For stores that need these features, you can add the Storefront API alongside the public JSON API. Use the public API for the 90% of pages that are read-only product displays, and use the Storefront API only for the features that require it. The architecture supports both. You are not forced into one or the other.

The Build Process

Starting a new project on this stack takes about 30 minutes to get to a deployable product listing page. Here is the sequence:

npx create-next-app@latest my-store --typescript --tailwind --app
cd my-store

# No Shopify packages to install
# No API keys to configure
# No environment variables to set

# Create lib/shopify.ts with fetch functions
# Create app/products/[handle]/page.tsx with generateStaticParams
# Create components/ProductCard.tsx
# Create lib/cart.ts with localStorage functions

# Deploy
vercel

# Done. Products are live. Cart works. Checkout redirects to Shopify.

There is no Shopify app to create. No API scopes to configure. No webhook endpoints to set up. No environment variables to manage across development, staging, and production. The store URL is the only configuration. It is hardcoded or pulled from a single environment variable.

For teams that are used to spending days setting up Shopify development environments, this feels almost suspicious. Where is the complexity? There is none. That is the point.

When to Use Something Else

This stack is optimized for stores that primarily display and sell products. It works best when:

  • Your catalog is under 5,000 products (ISR handles larger catalogs fine, but build times increase)
  • You do not need customer account features on the frontend
  • Your checkout happens on Shopify (not a custom checkout)
  • Your team knows React and TypeScript
  • You want maximum performance with minimum infrastructure

If you are building a marketplace with customer dashboards, subscription management, and complex account features, Hydrogen might actually be the better choice. If you are building a content-heavy site that happens to sell products, a CMS-first approach with Sanity or Contentful might make more sense.

But for the vast majority of Shopify stores that need a fast, custom frontend for product display and sales, this stack is the correct answer in 2026. It is boring on purpose. Every piece is proven, documented, and replaceable. That is exactly what you want for infrastructure that your revenue depends on.

Getting Started

If you are currently running a Shopify template and your conversion rate is lower than you think it should be, start here:

  1. Open your Shopify store URL and append /products.json. Look at the data. That is your entire product catalog in a structured format, available right now, with no setup.
  2. Spin up a Next.js project with create-next-app. Fetch that JSON in a server component. Render a product grid. Deploy to Vercel. The whole thing takes an afternoon.
  3. Compare the Lighthouse scores between your template and the Next.js version. The difference will make the business case for you.

If you want to skip the build and get a production-ready headless Shopify storefront with AI commerce, lead capture, CRM integration, and the full architecture described here, that is exactly what Cartridge does. Same stack. Pre-built. Config-driven. Ready to deploy.

The headless Shopify landscape is noisy. Ignore the noise. Use Next.js, the public JSON API, Tailwind, and Vercel. Ship fast. Score 95+ on Lighthouse. Redirect to Shopify checkout. Keep it simple. Keep it fast. Keep it yours.