Vodík: Shopify's Commerce Framework

Shopify vodík je oficiální rámec React pro budování výloh bezhlavý Shopify. V aktuální verzi (Hydrogen 2.x) je na něm postaveno Remixy, meta-rámec React, který využívá nativní webová rozhraní API a akce serveru pro architekturu postupně zlepšoval.

Kyslík je globální hostingový runtime Shopify pro projekty Hydrogen: na základě Pracovníci Cloudflare (V8 Isolates), spouští kód na straně serveru na okraji — v datových centrech nejblíže ke koncovému uživateli. Výsledek je jeden TTFB (Time to First Byte) 30-80 ms globálně, se serverem nelze dosáhnout tradiční centralizované.

Nastavení vodíkového projektu

# 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

Struktura projektu Hydrogen je založena na remixu a velmi podobná klasice souborové směrování:

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

Vodík se připojuje k Shopify přes Storefront API GraphQL. Každá operace commerce je dotaz nebo mutace GraphQL. Vodík poskytuje předkonfigurovaného klienta:

// 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: Spravujte svůj košík pomocí vodíku

Vodík řídí vozík přes Shopify Cart API (na základě GraphQL mutace). Rámec obsahuje optimistický systém uživatelského rozhraní pro okamžité aktualizace:

// 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>
  );
}

Ukládání kyslíku do mezipaměti: klíč k výkonu

Oxygen spouští vodíkový kód na hraně Cloudflare Workers. Cache je spravována přes Nativní API Cloudflare a pomocníci s vodíkem:

// 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
});

Téma migrace z kapaliny na vodík

Pokud máte existující téma Shopify, migrace na Hydrogen je významný projekt. Doporučený přístup je přírůstkový:

  1. Identifikujte prioritní stránky: stránky produktů a stránky kolekce mají větší návštěvnost. Začněte tam.
  2. Použijte vodík jako svou subdoménu: shop.tuodominio.com na vodík, tuodominio.com na téma Liquid. Testujte a měřte výkon.
  3. Migrujte pokladnu samostatně: Shopify zpracovává pokladny po svém nativní systém (Checkout Extensibility) — nemusíte jej přepisovat od začátku.
  4. Přeneste postupně: stránka po stránce, trasa po trase.

Omezení vodíku, která je třeba znát

  • Pokladna spravovaná službou Shopify: pokladna je vždy v doméně Shopify (checkout.shopify.com). Vzhled si můžete přizpůsobit pomocí služby Checkout Rozšiřitelnost, ale pokladnu za svou nenahradíte.
  • Částečné uzamčení dodavatele: Vodík/kyslík funguje pouze u Shopify backend. Pokud chcete změnit obchodní platformu, musíte přepsat frontend.
  • Limity rychlosti API: Storefront API má omezení rychlosti. Pro hustý provoz vysoké zvážit agresivní ukládání do mezipaměti nebo vzor BFF s ukládáním do mezipaměti na straně serveru.

Závěry a další kroky

Vodík + kyslík je nejpragmatičtější volbou pro ty, kteří již v ekosystému pracují Shopify a chce jít bezhlavě. Kombinace Remix, GraphQL, edge computing a Nativní správa pokladen nabízí vynikající základ pro vysoce výkonné výlohy.

V příštím článku prozkoumáme Medusa.js, nejlepší alternativa open source headless: zcela samoobslužná, modulární architektura a bez uzamčení dodavatele.