Hydrogen: Shopify's Commerce Framework

Shopify Hydrogen is the official React framework for building storefronts headless Shopify. In the current version (Hydrogen 2.x) it is built on Remixes, the React meta-framework that leverages native Web APIs and Server Actions for an architecture progressively improved.

Oxygen is Shopify's global hosting runtime for Hydrogen projects: based on Cloudflare Workers (V8 Isolates), runs server-side code at the edge — in the data centers closest to the end user. The result is one TTFB (Time to First Byte) of 30-80ms globally, impossible to achieve with a server traditional centralized.

Setup of a Hydrogen Project

# Crea un nuovo progetto Hydrogen
npm create @shopify/hydrogen@latest my-store

# Rispondi alle domande:
# - Store domain: mystore.myshopify.com
# - Storefront API token: [da Shopify Admin > Apps > Develop apps]
# - Language: TypeScript
# - CSS: Tailwind CSS

cd my-store
npm run dev
# Apre su http://localhost:3000

The structure of a Hydrogen project is based on Remix and very similar to the classic file-based routing:

app/
├── components/           # Componenti UI riutilizzabili
│   ├── Cart.tsx          # Componente carrello
│   ├── ProductCard.tsx   # Card prodotto
│   └── Header.tsx        # Header con mini-cart
├── routes/              # File-based routing Remix
│   ├── _index.tsx        # Homepage (/)
│   ├── products.$handle.tsx  # Pagina prodotto (/products/[handle])
│   ├── collections.$handle.tsx  # Collection (/collections/[handle])
│   └── cart.tsx          # Pagina carrello (/cart)
├── lib/
│   ├── fragments.ts     # GraphQL fragments riutilizzabili
│   └── utils.ts         # Utility functions
├── root.tsx             # Root layout
└── entry.server.tsx     # Server-side entry point

Storefront API: The Hydrogen Core

Hydrogen connects to Shopify via the Storefront API GraphQL. Every operation commerce is a GraphQL query or mutation. Hydrogen provides the pre-configured client:

// app/routes/products.$handle.tsx
// Pagina dettaglio prodotto

import {json} from '@shopify/remix-oxygen';
import {useLoaderData} from '@remix-run/react';
import {Image, Money} from '@shopify/hydrogen';
import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';

const PRODUCT_QUERY = `#graphql
  query Product($handle: String!, $selectedOptions: [SelectedOptionInput!]!) {
    product(handle: $handle) {
      id
      title
      descriptionHtml
      vendor
      selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {
        id
        availableForSale
        selectedOptions { name value }
        price { amount currencyCode }
        compareAtPrice { amount currencyCode }
        image { url altText width height }
      }
      options {
        name
        values
      }
      variants(first: 100) {
        nodes {
          id
          availableForSale
          selectedOptions { name value }
          price { amount currencyCode }
        }
      }
    }
  }
`;

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

  // Leggi le opzioni selezionate dalla URL (?color=red&size=M)
  const searchParams = new URL(request.url).searchParams;
  const selectedOptions = [];
  for (const [name, value] of searchParams) {
    selectedOptions.push({ name, value });
  }

  const { product } = await storefront.query(PRODUCT_QUERY, {
    variables: { handle, selectedOptions },
    cache: storefront.CacheShort(),    // cache di 1 minuto su Oxygen
  });

  if (!product) throw new Response('Not Found', { status: 404 });

  return json({ product });
}

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

  return (
    <div className="product">
      {selectedVariant?.image && (
        <Image
          data={selectedVariant.image}
          className="product-image"
          sizes="(min-width: 768px) 50vw, 100vw"
        />
      )}
      <h1>{product.title}</h1>
      {selectedVariant && (
        <Money data={selectedVariant.price} />
      )}
      <AddToCartButton variant={selectedVariant} />
    </div>
  );
}

Cart API: Manage Your Cart with Hydrogen

Hydrogen manages the cart via the Shopify's Cart API (based on GraphQL Mutations). The framework includes an optimistic UI system for immediate updates:

// Aggiungere un prodotto al carrello
const ADD_TO_CART_MUTATION = `#graphql
  mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
    cartLinesAdd(cartId: $cartId, lines: $lines) {
      cart {
        id
        totalQuantity
        cost {
          totalAmount { amount currencyCode }
        }
        lines(first: 100) {
          nodes {
            id
            quantity
            merchandise {
              ... on ProductVariant {
                id
                title
                product { title }
                price { amount currencyCode }
                image { url altText }
              }
            }
          }
        }
      }
    }
  }
`;

// Componente AddToCartButton con Fetcher di Remix
import { useFetcher } from '@remix-run/react';

function AddToCartButton({ variant }: { variant: ProductVariant }) {
  const fetcher = useFetcher();
  const isAdding = fetcher.state !== 'idle';

  return (
    <fetcher.Form method="POST" action="/cart">
      <input type="hidden" name="intent" value="add" />
      <input type="hidden" name="variantId" value={variant.id} />
      <button
        type="submit"
        disabled={!variant.availableForSale || isAdding}
        className="add-to-cart-btn"
      >
        {isAdding ? 'Aggiunta...' : 'Aggiungi al Carrello'}
      </button>
    </fetcher.Form>
  );
}

Caching on Oxygen: The Key to Performance

Oxygen runs Hydrogen code on the Cloudflare Workers edge. The cache is managed via Cloudflare native APIs and Hydrogen helpers:

// Strategie di cache disponibili in Hydrogen
// Definiamo cache personalizzate per tipo di contenuto

// Cache lunga per pagine prodotto (aggiornamento raro)
export async function loader({ context }: LoaderFunctionArgs) {
  const { storefront } = context;

  const data = await storefront.query(PRODUCTS_QUERY, {
    cache: storefront.CacheLong(),        // cache di 1 ora
  });

  return json(data, {
    headers: {
      'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
    },
  });
}

// Cache breve per dati variabili (inventory, prezzi)
const inventoryData = await storefront.query(INVENTORY_QUERY, {
  cache: storefront.CacheShort(),         // cache di 60 secondi
});

// Nessuna cache per dati personalizzati (cart, customer)
const customerData = await storefront.query(CUSTOMER_QUERY, {
  cache: storefront.NoStore(),            // no-cache
});

Migration from Liquid to Hydrogen theme

If you have an existing Shopify theme, migrating to Hydrogen is a significant project. The recommended approach is incremental:

  1. Identify priority pages: product pages and collection pages they receive more traffic. Start there.
  2. Use Hydrogen as your subdomain: shop.tuodominio.com on Hydrogen, tuodominio.com on the Liquid theme. Test and measure performance.
  3. Migrate checkout separately: Shopify handles checkout with its own native system (Checkout Extensibility) — you don't have to rewrite it from scratch.
  4. Transfer gradually: page by page, route by route.

Limitations of Hydrogen to Know

  • Checkout managed by Shopify: checkout is always on the domain Shopify (checkout.shopify.com). You can customize the look with Checkout Extensibility, but you can't replace the checkout with your own.
  • Partial vendor lock-in: Hydrogen/Oxygen only works with Shopify like backend. If you want to change commerce platform, you have to rewrite the frontend.
  • API rate limits: The Storefront API has rate limiting. For heavy traffic high consider aggressive caching or a BFF pattern with server-side caching.

Conclusions and Next Steps

Hydrogen + Oxygen is the most pragmatic choice for those already working in the ecosystem Shopify and wants to go headless. The combination of Remix, GraphQL, edge computing and the Native checkout management offers an excellent foundation for high-performance storefronts.

In the next article we will explore Medusa.js, the best open-source alternative headless: completely self-hostable, modular architecture and no vendor lock-in.

Previous article ← MACH Architecture