Headless commerce has been one of the most discussed topics in ecommerce development over the past five years. The promise is compelling: decouple your storefront from your commerce backend, gain complete creative freedom, deliver faster page loads, and build experiences that would be impossible within the constraints of a traditional theme architecture.

The reality is more nuanced. Headless commerce introduces significant complexity, requires a different skill set, costs more to build and maintain, and does not automatically deliver better performance or conversion rates. For some brands, it is transformative. For many others, it is an expensive detour from what would have been better achieved with a well-built custom Shopify theme.

This guide covers the technical foundations of Shopify Hydrogen — Shopify’s official headless framework — and provides practical guidance on architecture, implementation, and the critical decision of whether to go headless at all. We have built headless storefronts for brands where it was the right call, and we have talked brands out of it when it was not. Both conversations are equally important.

What headless commerce actually means

In a traditional Shopify setup, the frontend (what customers see and interact with) and the backend (product data, orders, inventory, checkout) are tightly coupled. Your Liquid theme templates render HTML on the server using data from the Shopify backend. This is monolithic architecture, and it works well for the vast majority of stores.

Headless commerce separates these layers. The Shopify backend continues to manage your products, orders, customers, and checkout. But the frontend is a completely separate application — typically a React or Vue.js application — that fetches data from Shopify via APIs and renders the storefront independently.

This separation creates several architectural implications:

  • Complete frontend freedom — you are not constrained by Liquid’s templating capabilities or Shopify’s theme architecture.
  • API-first data access — all product, collection, and customer data is fetched via the Storefront API using GraphQL.
  • Independent deployment — the frontend and backend can be updated and deployed independently.
  • Multi-channel potential — the same API layer can power web, mobile apps, kiosks, and other touchpoints.

Headless is not inherently better or worse than monolithic. It is a different architectural approach that trades simplicity for flexibility. The question is whether the flexibility justifies the trade-off for your specific use case.

Diagram comparing monolithic Shopify architecture with headless Hydrogen architecture
Monolithic vs headless: in a headless setup, the frontend is a separate application that communicates with Shopify exclusively through APIs.

Hydrogen architecture and Remix foundations

Hydrogen is Shopify’s opinionated React framework for building headless storefronts. It is built on top of Remix, a full-stack React framework that emphasises server-side rendering, progressive enhancement, and web standards.

Understanding Remix is essential to working with Hydrogen. Unlike client-side React frameworks like Next.js (prior to the App Router), Remix renders pages on the server by default and sends fully-formed HTML to the browser. This is critical for SEO and initial page load performance.

The Hydrogen stack

A Hydrogen application consists of several layers:

  • Remix — the underlying web framework handling routing, data loading, and rendering.
  • Hydrogen — Shopify-specific utilities, hooks, and components layered on top of Remix.
  • Storefront API client — a GraphQL client configured for the Shopify Storefront API.
  • Oxygen — Shopify’s hosting platform for Hydrogen, providing global edge deployment.

Project scaffolding

Creating a new Hydrogen project is straightforward with the Shopify CLI:

npm create @shopify/hydrogen@latest -- --template demo-store

# Or with a minimal template
npm create @shopify/hydrogen@latest -- --template skeleton

The demo-store template provides a fully functional storefront with product listings, collections, cart, and search. The skeleton template gives you a minimal starting point. For production projects, we typically start with the skeleton template and build up, rather than stripping down the demo store.

Configuration

Hydrogen requires configuration to connect to your Shopify store. This happens in server.ts (or server.js):

// server.ts
import {createStorefrontClient} from '@shopify/hydrogen';

const {storefront} = createStorefrontClient({
  storeDomain: env.PUBLIC_STORE_DOMAIN,
  storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
  storefrontApiVersion: '2026-01',
});

The PUBLIC_STOREFRONT_API_TOKEN is a public token (safe to expose in client-side code) that provides read-only access to your store’s product catalogue, collections, and other public data. Mutations like cart operations use the same token but write to session-specific data.

Hydrogen project structure showing routes, components, and API configuration
A typical Hydrogen project structure follows Remix conventions with routes, components, and a server configuration layer.

Working with the Storefront API

The Storefront API is a GraphQL API that provides access to your store’s public data. Unlike the Admin API (which is authenticated and provides full read/write access), the Storefront API is designed for customer-facing applications.

Every data fetch in a Hydrogen storefront goes through the Storefront API. Understanding GraphQL is therefore a prerequisite for Hydrogen development — there is no REST alternative for the Storefront API. If your team is more comfortable with REST, consider whether the Shopify Admin API approach with a custom backend might be simpler.

Querying product data

Here is how you fetch a product by handle in a Hydrogen route loader:

// app/routes/products.$handle.tsx
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';

const PRODUCT_QUERY = `#graphql
  query Product($handle: String!) {
    product(handle: $handle) {
      id
      title
      handle
      description
      descriptionHtml
      vendor
      productType
      tags
      priceRange {
        minVariantPrice {
          amount
          currencyCode
        }
        maxVariantPrice {
          amount
          currencyCode
        }
      }
      images(first: 10) {
        nodes {
          id
          url
          altText
          width
          height
        }
      }
      variants(first: 50) {
        nodes {
          id
          title
          availableForSale
          price {
            amount
            currencyCode
          }
          selectedOptions {
            name
            value
          }
        }
      }
      seo {
        title
        description
      }
    }
  }
`;

export async function loader({params, context}: LoaderFunctionArgs) {
  const {handle} = params;
  const {product} = await context.storefront.query(PRODUCT_QUERY, {
    variables: {handle},
  });

  if (!product) {
    throw new Response('Product not found', {status: 404});
  }

  return json({product});
}

This pattern — defining a GraphQL query and executing it in a Remix loader — is the fundamental data-fetching pattern in Hydrogen. The loader runs on the server, meaning the query executes server-side and the result is serialised and sent to the client as part of the initial HTML response.

Query optimisation

GraphQL’s strength is requesting only the fields you need. A common mistake in Hydrogen development is over-fetching — requesting every field on a product when you only need the title and price for a collection listing.

Create separate queries for different contexts:

// Minimal query for collection product cards
const PRODUCT_CARD_FRAGMENT = `#graphql
  fragment ProductCard on Product {
    id
    title
    handle
    featuredImage {
      url
      altText
      width
      height
    }
    priceRange {
      minVariantPrice {
        amount
        currencyCode
      }
    }
    variants(first: 1) {
      nodes {
        availableForSale
      }
    }
  }
`;

This approach keeps your API responses lean and your pages fast. For collection pages listing dozens of products, the difference between fetching 5 fields and 50 fields per product is significant.

Routing and data loading patterns

Hydrogen uses Remix’s file-based routing system. Every file in the app/routes directory becomes a route in your application. The naming convention determines the URL structure:

app/routes/
├── _index.tsx           → /
├── products.$handle.tsx → /products/:handle
├── collections._index.tsx → /collections
├── collections.$handle.tsx → /collections/:handle
├── pages.$handle.tsx    → /pages/:handle
├── search.tsx           → /search
├── cart.tsx              → /cart
└── account.tsx          → /account

Each route file can export a loader function (for server-side data fetching), a meta function (for SEO metadata), and a default component (for rendering).

Nested routes and layouts

Remix’s nested routing system allows you to share layouts between routes. This is particularly useful for consistent headers, footers, and navigation across your storefront:

// app/root.tsx — the top-level layout
export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Header />
        <main>
          <Outlet />  {/* Child routes render here */}
        </main>
        <Footer />
        <Scripts />
      </body>
    </html>
  );
}

Streaming and deferred data

One of Remix’s most powerful features is streaming. You can defer non-critical data so that the page shell loads immediately while secondary data streams in:

import {defer} from '@shopify/remix-oxygen';
import {Await} from '@remix-run/react';
import {Suspense} from 'react';

export async function loader({context}: LoaderFunctionArgs) {
  // Critical data — blocks rendering
  const product = await context.storefront.query(PRODUCT_QUERY, {
    variables: {handle},
  });

  // Non-critical data — streams in later
  const recommendations = context.storefront.query(
    RECOMMENDED_PRODUCTS_QUERY,
    {variables: {productId: product.id}}
  );

  return defer({product, recommendations});
}

export default function Product() {
  const {product, recommendations} = useLoaderData<typeof loader>();

  return (
    <>
      <ProductDetails product={product} />
      <Suspense fallback={<LoadingSpinner />}>
        <Await resolve={recommendations}>
          {(data) => <RecommendedProducts products={data} />}
        </Await>
      </Suspense>
    </>
  );
}

This pattern is particularly effective for product pages where the main product data needs to load immediately for SEO and user experience, but related products and reviews can load progressively.

Data loading waterfall comparing blocking and streaming approaches in Hydrogen
Streaming allows the page shell and critical content to render immediately whilst non-critical data loads in the background.

Cart management and checkout

Cart management in Hydrogen is handled through the Storefront API’s Cart object. Hydrogen provides a CartForm component and cart utilities that abstract the underlying GraphQL mutations:

import {CartForm} from '@shopify/hydrogen';

function AddToCartButton({variantId, quantity = 1}) {
  return (
    <CartForm
      route="/cart"
      action={CartForm.ACTIONS.LinesAdd}
      inputs={{
        lines: [{merchandiseId: variantId, quantity}],
      }}
    >
      <button type="submit">Add to cart</button>
    </CartForm>
  );
}

The cart is session-based, stored on Shopify’s servers, and identified by a cart ID stored in a cookie. This is fundamentally different from client-side cart implementations where cart state lives in local storage or application state.

Checkout

One of the most important things to understand about headless Shopify: you cannot build a custom checkout. Checkout is handled by Shopify. When a customer is ready to purchase, they are redirected to Shopify’s checkout domain (checkout.shopify.com or your custom domain’s checkout). This applies to all Shopify stores, whether headless or not.

You can customise the checkout through checkout extensibility (Shopify Plus required), but you cannot replace it entirely. This is actually a significant advantage — Shopify’s checkout is one of the highest-converting in ecommerce, and maintaining PCI compliance for a custom checkout is a massive undertaking.

SEO in a headless Hydrogen storefront

SEO is where headless storefronts require the most careful attention. With a Liquid theme, Shopify handles many SEO fundamentals automatically: meta tags, canonical URLs, sitemaps, and robots.txt. With Hydrogen, you are responsible for all of it.

Meta tags and the meta function

Every route should export a meta function that returns appropriate SEO metadata:

export const meta: MetaFunction<typeof loader> = ({data}) => {
  if (!data?.product) return [{title: 'Product Not Found'}];

  const {product} = data;
  return [
    {title: `${product.seo.title || product.title} | Your Store`},
    {name: 'description', content: product.seo.description || product.description?.substring(0, 155)},
    {property: 'og:title', content: product.seo.title || product.title},
    {property: 'og:description', content: product.seo.description || product.description?.substring(0, 155)},
    {property: 'og:type', content: 'product'},
    {property: 'og:image', content: product.images.nodes[0]?.url},
    {name: 'twitter:card', content: 'summary_large_image'},
    {tagName: 'link', rel: 'canonical', href: `https://yourstore.com/products/${product.handle}`},
  ];
};

Structured data

You need to manually implement JSON-LD structured data. Hydrogen does not generate this for you. Create a reusable component for each schema type:

function ProductSchema({product}: {product: Product}) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.title,
    description: product.description,
    image: product.images.nodes[0]?.url,
    brand: {
      '@type': 'Brand',
      name: product.vendor,
    },
    offers: {
      '@type': 'Offer',
      price: product.priceRange.minVariantPrice.amount,
      priceCurrency: product.priceRange.minVariantPrice.currencyCode,
      availability: product.variants.nodes[0]?.availableForSale
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock',
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{__html: JSON.stringify(schema)}}
    />
  );
}

For comprehensive guidance on implementing structured data for Shopify, see our guide on technical SEO for Shopify.

Sitemaps

You need to build your own sitemap generator. This typically involves creating a route that queries the Storefront API for all products, collections, and pages, then returns XML:

// app/routes/[sitemap.xml].tsx
export async function loader({context}: LoaderFunctionArgs) {
  const {products} = await context.storefront.query(ALL_PRODUCTS_QUERY);
  const {collections} = await context.storefront.query(ALL_COLLECTIONS_QUERY);

  const urls = [
    ...products.nodes.map((p) => ({
      loc: `https://yourstore.com/products/${p.handle}`,
      lastmod: p.updatedAt,
    })),
    ...collections.nodes.map((c) => ({
      loc: `https://yourstore.com/collections/${c.handle}`,
      lastmod: c.updatedAt,
    })),
  ];

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      ${urls.map((u) => `<url><loc>${u.loc}</loc><lastmod>${u.lastmod}</lastmod></url>`).join('')}
    </urlset>`;

  return new Response(xml, {
    headers: {'Content-Type': 'application/xml'},
  });
}
SEO considerations for headless Hydrogen storefronts showing meta tags, schema, and sitemap requirements
Going headless means taking full responsibility for SEO fundamentals that Liquid themes handle automatically.

Deploying to Oxygen

Oxygen is Shopify’s hosting platform for Hydrogen. It provides globally distributed edge workers that run your Remix server-side code close to your customers. Deployment is tightly integrated with the Shopify CLI and Git.

Deployment workflow

# Deploy from the CLI
npx shopify hydrogen deploy

# Or connect a Git repository for automatic deployments
# Every push to main triggers a production deployment
# Pull requests create preview deployments

Oxygen provides environment variables management through the Shopify admin, preview deployments for pull requests, and automatic SSL certificates. If you are deploying to Oxygen, you get a managed hosting experience without needing to configure Cloudflare Workers, Vercel, or AWS yourself.

Alternative hosting

Hydrogen is a Remix application, so it can be deployed anywhere that supports Remix: Vercel, Cloudflare Workers, Node.js servers, or Docker containers. If you choose an alternative to Oxygen, you lose the tight Shopify admin integration but gain flexibility in your deployment pipeline.

For most Hydrogen projects, we recommend starting with Oxygen. The integration with Shopify’s infrastructure reduces operational complexity, and the global edge deployment provides strong performance out of the box.

Performance optimisation strategies

One of the primary arguments for headless commerce is performance. However, a poorly built Hydrogen storefront can easily be slower than a well-optimised Liquid theme. Performance does not come for free — it requires deliberate architecture decisions.

Caching

Hydrogen provides built-in caching utilities for Storefront API responses:

import {CacheShort, CacheLong} from '@shopify/hydrogen';

// Short cache for frequently changing data (cart, inventory)
const cart = await context.storefront.query(CART_QUERY, {
  cache: CacheShort(),
});

// Long cache for stable data (product details, collections)
const product = await context.storefront.query(PRODUCT_QUERY, {
  variables: {handle},
  cache: CacheLong(),
});

Caching strategy is critical. Cache too aggressively and customers see stale prices or stock status. Cache too little and every page load triggers fresh API calls, increasing latency and Storefront API usage.

Image optimisation

Use Hydrogen’s Image component for automatic responsive images through Shopify’s CDN:

import {Image} from '@shopify/hydrogen';

<Image
  data={product.featuredImage}
  sizes="(min-width: 768px) 50vw, 100vw"
  loading="lazy"
/>

This component automatically generates srcset attributes with multiple image sizes and serves images in modern formats (WebP, AVIF) through Shopify’s CDN.

Code splitting

Remix automatically code-splits by route, so each page only loads the JavaScript needed for that page. Beyond this, be intentional about third-party dependencies. Every npm package you add increases your bundle size. Monitor your bundle with the Remix visualiser and remove unused dependencies regularly.

For a deeper understanding of performance metrics on Shopify, read our guide on speeding up your Shopify store.

Performance comparison between a well-optimised Liquid theme and a Hydrogen storefront
Performance gains from headless are not automatic. They require deliberate caching, code splitting, and optimisation strategies.

When headless makes sense (and when it does not)

After building both headless and monolithic Shopify stores for over a decade, here is our honest assessment of when each approach is appropriate.

Headless makes sense when:

  • You need highly custom experiences — product configurators, 3D viewers, interactive lookbooks, or editorial-driven shopping experiences that cannot be built within Liquid’s constraints.
  • You are building multi-channel — the same commerce backend needs to power a website, mobile app, and potentially other touchpoints like kiosks or smart displays.
  • Your content model is complex — you are pulling content from a headless CMS (Contentful, Sanity, Prismic) alongside Shopify data, and you need fine-grained control over how they combine.
  • You have the team — you have (or can hire) React developers who can maintain the storefront long-term. This is not a build-and-forget project.
  • Your budget supports it — expect to spend 2–3 times more on the initial build compared to a Liquid theme, plus higher ongoing maintenance costs.

Headless does not make sense when:

  • You want a standard ecommerce experience — if your store follows conventional ecommerce patterns (product listings, collection pages, cart, checkout), a Liquid theme delivers the same outcome at a fraction of the cost.
  • Your team is not technical — headless storefronts cannot be edited by non-developers. There is no theme editor, no drag-and-drop. Every change requires a code deployment.
  • You are under £5M annual revenue — the economics rarely justify the investment at this scale. The additional build and maintenance costs outweigh the marginal gains.
  • SEO is your primary growth channel — while Hydrogen handles SEO well technically, you are taking on responsibility for every SEO element that Shopify handles automatically with Liquid. Any oversight can cost organic traffic.

If you are evaluating whether headless is right for your brand, start by understanding what your current Shopify Plus capabilities can achieve. You might be surprised by how much is possible without going headless.

The hybrid approach

The hybrid approach is increasingly popular and, in our experience, often the right answer. Rather than going fully headless or fully monolithic, you use Hydrogen for specific pages or experiences that benefit from custom frontend development, whilst keeping the rest of your store on a Liquid theme.

Common hybrid patterns

  • Editorial content hub — a Hydrogen-powered editorial experience with rich media, animations, and content from a headless CMS, sitting alongside a standard Liquid-powered product catalogue.
  • Product configurator — a complex product customisation tool built in React via Hydrogen, linked to from standard product pages.
  • Campaign landing pages — bespoke, highly interactive campaign experiences that would be impractical to build in Liquid.
  • Headless blog — a rich editorial experience powered by a headless CMS and rendered through Hydrogen, whilst the shop itself runs on Liquid.

The hybrid approach gives you the best of both worlds: the stability, simplicity, and lower cost of Liquid for your core commerce experience, with the flexibility of React for specific high-value pages.

If you are considering the technical capabilities of enterprise Shopify for your growing brand, the hybrid approach lets you adopt headless incrementally rather than committing to a full rewrite.


Hydrogen represents a significant investment in making headless commerce accessible for Shopify merchants. The framework is mature, well-documented, and backed by strong tooling. But the decision to go headless should be driven by genuine technical or business requirements, not by industry hype or the assumption that headless automatically means better.

Start with the question: what does my store need to do that it cannot do today? If the answer involves complex, interactive experiences that push beyond Liquid’s capabilities, Hydrogen is worth exploring. If the answer is primarily about speed, design flexibility, or a more modern tech stack, you may find that a well-built custom theme achieves the same goals with less risk and lower cost.

We help brands navigate this decision every week. If you want a clear-eyed assessment of whether headless makes sense for your Shopify development roadmap, start a conversation with us. We will give you an honest answer, even if that answer is “do not go headless.”