Skip to main content

Hydrogen and Headless Commerce

Not every storefront fits the Liquid theme model. When brands need pixel-perfect custom designs, app-like interactivity, or integration with non-Shopify data sources, they go headless. Hydrogen is Shopify's answer -- a React framework built on Remix, purpose-built for headless Shopify commerce. This lesson covers when and how to use Hydrogen, its architecture, and how to build your first headless storefront.

What Is Headless Commerce?

In a traditional Shopify setup, the storefront (what customers see) and the backend (where data lives) are tightly coupled. Shopify renders Liquid templates on the server and delivers complete HTML pages.

In headless commerce, the storefront is decoupled from the backend. Your custom frontend fetches data from Shopify via the Storefront API and renders the UI however you want. Shopify still handles products, orders, checkout, and payments -- you just control the presentation layer.

When to Use Hydrogen vs Liquid Themes

ScenarioRecommendation
Standard e-commerce storeLiquid theme
Merchant manages content via theme editorLiquid theme
Budget-conscious projectLiquid theme
Completely custom design systemHydrogen
App-like interactions (animations, transitions)Hydrogen
Multi-source content (CMS + Shopify + custom APIs)Hydrogen
Development team with React expertiseHydrogen
Multiple storefronts from one Shopify backendHydrogen
High-performance requirements (streaming SSR)Hydrogen
Most Stores Should Use Liquid

Hydrogen is powerful, but it is not always the right choice. Liquid themes are faster to build, easier for merchants to maintain, and cover 90% of use cases. Choose Hydrogen when you have a specific need that Liquid cannot meet and a team that can support a React application long-term.

Hydrogen Architecture

Hydrogen is built on top of Remix, the full-stack React framework. If you know Remix, you already know most of Hydrogen. Shopify adds a layer of commerce-specific utilities on top.

Key Hydrogen Packages

  • @shopify/hydrogen: Core commerce components and utilities (Cart, Analytics, SEO, Money, Image)
  • @shopify/hydrogen-react: Framework-agnostic React hooks for Shopify (can be used outside Hydrogen)
  • @shopify/remix-oxygen: Remix adapter for Oxygen hosting
  • @shopify/cli-hydrogen: CLI commands for creating and managing Hydrogen projects

Creating a Hydrogen Project

# Create a new Hydrogen storefront
npm create @shopify/hydrogen@latest -- --template demo-store

# The CLI prompts:
# ? Where would you like to create your app? hydrogen-store
# ? Choose a language: TypeScript
# ? Connect to Shopify: Use mock.shop (or connect your store)

Project Structure

hydrogen-store/
├── app/
│ ├── components/ # Reusable UI components
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ ├── ProductCard.tsx
│ │ └── CartDrawer.tsx
│ ├── routes/ # File-based routing (Remix)
│ │ ├── ($locale)._index.tsx # Homepage
│ │ ├── ($locale).products.$handle.tsx # Product page
│ │ ├── ($locale).collections.$handle.tsx
│ │ ├── ($locale).cart.tsx
│ │ ├── ($locale).account.tsx
│ │ └── [sitemap.xml].tsx
│ ├── lib/
│ │ └── fragments.ts # Reusable GraphQL fragments
│ ├── styles/
│ │ └── app.css
│ ├── entry.server.tsx # Server entry point
│ └── root.tsx # Root layout
├── public/
│ └── favicon.svg
├── server.ts # Oxygen/Node server
├── storefrontapi.generated.d.ts # Auto-generated types
├── .env # Environment variables
└── hydrogen.config.ts # Hydrogen configuration

Storefront API Integration

Hydrogen uses the Storefront API for all data fetching. The API client is set up in the server entry and passed to route loaders via Remix context.

Configuring the Client

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

const {storefront} = createStorefrontClient({
storeDomain: env.PUBLIC_STORE_DOMAIN,
publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
storefrontApiVersion: '2026-04',
});

Fetching Data in Route Loaders

Hydrogen follows the Remix pattern of loading data in loader functions that run on the server:

// app/routes/($locale).products.$handle.tsx
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {useLoaderData} from '@remix-run/react';
import {Image, Money, ShopPayButton} from '@shopify/hydrogen';

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

export async function loader({params, context}: LoaderFunctionArgs) {
const {handle} = params;
const {storefront} = context;

const {product} = await storefront.query(PRODUCT_QUERY, {
variables: {handle},
cache: storefront.CacheLong(),
});

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

return json({product});
}

export default function ProductPage() {
const {product} = useLoaderData<typeof loader>();
const firstVariant = product.variants.nodes[0];

return (
<div className="product-page">
<div className="product-grid">
<div className="product-image">
{product.featuredImage && (
<Image
data={product.featuredImage}
sizes="(min-width: 768px) 50vw, 100vw"
/>
)}
</div>

<div className="product-info">
<h1>{product.title}</h1>
<p className="vendor">{product.vendor}</p>

<Money
data={firstVariant.price}
className="product-price"
/>

<div
className="product-description"
dangerouslySetInnerHTML={{
__html: product.descriptionHtml,
}}
/>

<ShopPayButton
variantIds={[firstVariant.id]}
storeDomain={context.env.PUBLIC_STORE_DOMAIN}
/>
</div>
</div>
</div>
);
}

Caching Strategies

Hydrogen provides built-in caching utilities that are critical for performance:

// Cache strategies available on the storefront client
storefront.query(query, {
cache: storefront.CacheNone(), // No caching (real-time data)
cache: storefront.CacheShort(), // 1 second stale, 60 seconds max
cache: storefront.CacheLong(), // 1 hour stale, 1 day max
cache: storefront.CacheCustom({
mode: 'public',
maxAge: 60, // seconds
staleWhileRevalidate: 300,
}),
});
Cache Strategy Guidelines
  • Product pages: CacheLong() -- product data changes infrequently
  • Collection pages: CacheLong() -- similar to products
  • Cart: CacheNone() -- must always be real-time
  • Search results: CacheShort() -- fresh but can tolerate brief staleness
  • Homepage: CacheLong() with manual invalidation on content changes

Server-Side Rendering and Streaming

Hydrogen leverages Remix's streaming SSR to deliver fast initial page loads. Instead of waiting for all data to resolve before sending any HTML, the server streams the HTML shell immediately and fills in dynamic content as data arrives.

// app/routes/($locale).collections.$handle.tsx
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {Await, useLoaderData} from '@remix-run/react';
import {Suspense} from 'react';

export async function loader({params, context}: LoaderFunctionArgs) {
const {handle} = params;
const {storefront} = context;

// Critical data: await immediately
const collection = await storefront.query(COLLECTION_QUERY, {
variables: {handle, first: 12},
});

// Non-critical data: defer for streaming
const recommendations = storefront.query(RECOMMENDATIONS_QUERY, {
variables: {handle},
});

return defer({
collection: collection.collection,
recommendations, // This is a Promise, not awaited
});
}

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

return (
<div>
<h1>{collection.title}</h1>

{/* Critical content renders immediately */}
<ProductGrid products={collection.products.nodes} />

{/* Deferred content streams in when ready */}
<Suspense fallback={<RecommendationsSkeleton />}>
<Await resolve={recommendations}>
{(data) => (
<RecommendedProducts products={data.products.nodes} />
)}
</Await>
</Suspense>
</div>
);
}

This pattern ensures customers see meaningful content within milliseconds, even if some data takes longer to load.

Oxygen Hosting

Oxygen is Shopify's edge hosting platform, purpose-built for Hydrogen storefronts.

Deployment

Oxygen integrates with GitHub for automatic deployments:

  1. Connect your GitHub repository in the Shopify Admin under Sales Channels > Hydrogen
  2. Push to your main branch -- Oxygen deploys automatically
  3. Push to any other branch -- Oxygen creates a preview deployment
# Manual deployment via Shopify CLI
shopify hydrogen deploy

Environment Variables

Manage environment variables through the Shopify Admin or CLI:

# Set environment variables for production
shopify hydrogen env push

# Pull environment variables to local .env
shopify hydrogen env pull

Why Choose Oxygen

  • Zero configuration: No server setup, no CDN configuration, no scaling decisions
  • Global edge network: Workers run close to customers worldwide
  • Shopify-optimized: Lowest latency path to Shopify's Storefront API
  • Preview deployments: Every branch gets a unique URL for review
  • Built-in analytics: Performance monitoring in the Shopify Admin
Oxygen Is Not Required

You can deploy Hydrogen to any Node.js hosting provider -- Vercel, Netlify, Cloudflare Workers, Fly.io, or your own servers. Oxygen is convenient and optimized for Shopify, but it is not a lock-in.

Starter Templates

Shopify provides several Hydrogen starter templates:

TemplateDescription
demo-storeFull-featured storefront with all pages
hello-worldMinimal starting point
skeletonBasic structure with routing, no styling
# Create from a specific template
npm create @shopify/hydrogen@latest -- --template demo-store

# Or clone and customize
npm create @shopify/hydrogen@latest -- --template skeleton

The demo-store template includes product pages, collection pages, cart functionality, customer accounts, search, blog, and SEO -- a complete storefront you can customize.

Key Takeaways

  • Hydrogen is a React + Remix framework for building headless Shopify storefronts
  • Use it when you need complete design control, React expertise, or multi-source data integration
  • Storefront API is the data layer -- accessed via typed GraphQL queries in route loaders
  • Streaming SSR with defer and Await delivers fast initial page loads
  • Oxygen provides zero-config edge hosting with GitHub integration and preview deployments
  • Most stores should use Liquid themes -- Hydrogen is for specific high-end use cases

Next, we explore Shopify Functions -- the WebAssembly runtime that lets you customize commerce logic directly inside Shopify's infrastructure.