Hydrogen è il toolkit con cui Shopify aiuta a costruire storefront headless: carrello, varianti, cache. Qui vedi che cosa fa, come funziona la sua cache e quali scelte il passaggio di versione 2026-10 ti obbliga a fare.
Che cosa è Hydrogen oggi, e perché la risposta è cambiata
Se hai letto guide su Hydrogen scritte negli anni scorsi, ricorderai un framework React basato su Remix. La documentazione ufficiale consultata il 2026-11-12 racconta una cosa diversa: per la versione 2026-10 Hydrogen è un insieme di strumenti che gestisce «le parti di una storefront che sono specifiche di Shopify e facili da sbagliare» (carrello, selezione delle varianti, filtri, analitiche e le rotte da cui dipendono app e checkout), e lascia routing, rendering e interfaccia al framework che scegli tra Next.js, React Router, Nuxt, Astro e SvelteKit. Le versioni 2026-04 e precedenti sono ora chiamate legacy e hanno una guida di migrazione.
La conseguenza per te è pratica: prima di copiare un listato da una guida, guarda la versione di Hydrogen a cui si riferisce. Un tutorial sul vecchio modello usa funzioni, importazioni e convenzioni di cartelle che nella documentazione attuale non sono più il percorso principale. Questo articolo mostra la logica che resta valida (cache, carrello, checkout) e dice di volta in volta a quale versione si riferisce il codice.
Il disegno a tre livelli descritto dalla documentazione aiuta a non confondersi. Il tuo framework possiede routing, rendering e dati; Hydrogen possiede la logica Shopify, cioè il client della Storefront API, il carrello e le rotte che servono a negozio e checkout (come /api/cart e /checkout);
Shopify fornisce la Storefront API, la Customer Account API e il checkout. Il checkout, quindi, non lo scrivi: lo raggiungi. Ti conviene ricordarlo prima di promettere a un cliente un checkout completamente su misura. Per un esempio di storefront con un framework scelto da te, vedi l'articolo su Next.js App Router.
La cache: perché una pagina prodotto non deve chiamare Shopify a ogni visita
Ogni pagina di prodotto, se non fai nulla, genera una richiesta alla Storefront API a ogni visita. Per pagine che cambiano di rado è uno spreco e un rischio di superare i limiti di richieste. La documentazione sulla
cache di Hydrogen e Oxygen (nella sezione legacy) elenca quattro strategie, tutte basate sull'intestazione Cache-Control: CacheShort() equivale a public, max-age=1, stale-while-revalidate=9, cioè dieci secondi in tutto;
CacheLong() a public, max-age=3600, stale-while-revalidate=82800, un giorno; CacheNone() a no-store; CacheCustom() lascia a te l'intestazione.
Quando non scegli, ogni sotto-richiesta usa max-age=1, stale-while-revalidate=86399.
La parte che sorprende è stale-while-revalidate, definita nella RFC 5861: dopo la scadenza del max-age, per la durata indicata la cache può ancora servire la copia vecchia mentre ne richiede una nuova all'origine.
Chi visita non aspetta, e la risposta successiva è aggiornata. Il listato qui sotto non usa Hydrogen: è un modello di quella regola, scritto per leggere un'intestazione e dire in che stato si trova una risposta dopo un certo numero di secondi.
type Esito = 'fresca' | 'stale: servita e rivalidata in background' | 'scaduta: si torna all origine';
// Legge max-age e stale-while-revalidate da un header Cache-Control.
function leggi(header: string) {
const val = (nome: string) => Number(new RegExp(`${nome}=(\\d+)`).exec(header)?.[1] ?? 0);
return { maxAge: val('max-age'), swr: val('stale-while-revalidate') };
}
// Stato di una risposta in cache, dopo `eta` secondi dalla sua produzione.
function stato(header: string, eta: number): Esito {
const { maxAge, swr } = leggi(header);
if (eta <= maxAge) return 'fresca';
if (eta <= maxAge + swr) return 'stale: servita e rivalidata in background';
return 'scaduta: si torna all origine';
}
const strategie = {
CacheShort: 'public, max-age=1, stale-while-revalidate=9',
CacheLong: 'public, max-age=3600, stale-while-revalidate=82800',
};
for (const [nome, header] of Object.entries(strategie)) {
for (const eta of [0.5, 5, 11, 3_599, 7_200, 86_401]) {
console.log(`${nome.padEnd(10)} eta ${String(eta).padStart(6)} s -> ${stato(header, eta)}`);
}
}
// Output:
// CacheShort eta 0.5 s -> fresca
// CacheShort eta 5 s -> stale: servita e rivalidata in background
// CacheShort eta 11 s -> scaduta: si torna all origine
// CacheShort eta 3599 s -> scaduta: si torna all origine
// CacheShort eta 7200 s -> scaduta: si torna all origine
// CacheShort eta 86401 s -> scaduta: si torna all origine
// CacheLong eta 0.5 s -> fresca
// CacheLong eta 5 s -> fresca
// CacheLong eta 11 s -> fresca
// CacheLong eta 3599 s -> fresca
// CacheLong eta 7200 s -> stale: servita e rivalidata in background
// CacheLong eta 86401 s -> scaduta: si torna all origine
Le righe che contano sono la lettura dei due numeri e le due soglie in stato. Una risposta con CacheShort è fresca solo fino a 1 secondo, resta servibile ma in rivalidazione fino a 10, poi è scaduta e l'utente aspetta
la richiesta all'origine; con CacheLong la finestra di freschezza è un'ora e quella di rivalidazione arriva a un giorno. Mi pare utile vedere che a 7.200 secondi una pagina «lunga» è già vecchia ma ancora servita:
se il prezzo di un prodotto cambia, un cliente può vederne uno vecchio fino a quando la rivalidazione non finisce. L'uscita è deterministica, eseguita con Node 24, e simula soltanto la regola dell'RFC: non misura nessun negozio Shopify. Quanto la cache pesi sul LCP si misura nell'articolo su Core Web Vitals e SEO.
Un loader con cache, e perché non tutto va in cache
Il listato seguente mostra come si assegnava la strategia a una richiesta nel modello legacy: l'oggetto storefront, preso dal contesto del server, ha il metodo query a cui passi la query GraphQL, le variabili e l'opzione
cache. Qui il titolo e le varianti di un prodotto usano CacheLong() perché cambiano di rado. Se il prodotto non esiste, il loader lancia una risposta 404 vera: una pagina «non trovato» con stato 200 sarebbe indicizzabile come contenuto duplicato.
// app/routes/products.$handle.tsx - stile Hydrogen 2026-04 e precedenti (legacy), con React Router
import type { LoaderFunctionArgs } from '@shopify/remix-oxygen';
const PRODUCT_QUERY = `#graphql
query Product($handle: String!) {
product(handle: $handle) {
id
title
descriptionHtml
variants(first: 20) {
nodes { id availableForSale price { amount currencyCode } }
}
}
}
`;
export async function loader({ params, context }: LoaderFunctionArgs) {
const { storefront } = context;
const { product } = await storefront.query(PRODUCT_QUERY, {
variables: { handle: params.handle },
cache: storefront.CacheLong(), // titoli e descrizioni cambiano di rado
});
if (!product) throw new Response('Not Found', { status: 404 });
return { product };
}
Questo blocco non è stato eseguito: richiede un negozio Shopify con un token e il progetto Hydrogen intero, quindi va letto come schema da adattare, e la forma esatta degli import dipende dalla versione di Hydrogen e dal framework.
Il principio, invece, non dipende dalla versione: la disponibilità e il prezzo cambiano più spesso di titolo e descrizione, perciò non hanno la stessa strategia. La documentazione avverte inoltre che i dati della Customer Account API non vengono mai messi in cache, perché sono personali:
il carrello e l'account del cliente vanno con CacheNone() o fuori dalla cache. Mettere in cache una risposta personalizzata e servirla a un altro visitatore è l'errore più grave che questo schema permette.
Il carrello: un'operazione GraphQL, e dove nasce l'errore
Il carrello di Hydrogen è costruito, secondo la pagina dedicata, in modo «server-side first»: moduli HTML e richieste HTTP normali, arricchiti nel browser con aggiornamenti ottimistici e indicatori di caricamento.
Il vantaggio dichiarato è che continua a funzionare se JavaScript non si carica. Sotto, l'operazione che aggiunge una riga è la mutazione cartLinesAdd della Storefront API, che accetta l'identificativo del carrello e una lista di righe, ciascuna con merchandiseId (la variante) e quantity.
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
totalQuantity
cost { totalAmount { amount currencyCode } }
}
userErrors { field message }
}
}
# variabili:
# { "cartId": "gid://shopify/Cart/...", "lines": [ { "merchandiseId": "gid://shopify/ProductVariant/...", "quantity": 1 } ] }
Anche questo è uno schema non eseguito, perché richiede un carrello e un token reali. La riga a cui fare attenzione è userErrors: la documentazione la definisce come lista non nulla, quindi esiste sempre, e se la variante non è disponibile
o la quantità non è valida l'errore arriva lì dentro, non come errore HTTP. Un codice che legge solo cart mostrerebbe un carrello invariato senza spiegare perché. L'altro limite scritto nella documentazione è che una singola chiamata non può contenere più di 250 righe.
Il costo del legame con Shopify
Hydrogen funziona con Shopify come backend e solo con quello: le Storefront API, il carrello e il checkout sono di Shopify. Se un giorno cambi piattaforma, il livello di presentazione (componenti, stile, routing) si può spesso conservare, mentre tutta la parte di dati va riscritta. È un legame accettabile se hai scelto Shopify per il suo ecosistema (app, pagamenti, checkout già maturo) e scomodo se il motivo per cui vai headless è proprio non dipendere da un unico fornitore: in quel caso guarda Medusa o Saleor.
Resta il tema dei limiti di richieste. Nella pagina della Storefront API, per il client Ruby che gira sul server, si legge che il limite si basa sull'indirizzo IP di chi fa la richiesta, cioè quello del server; l'accesso con token pubblico, invece, ha una capacità che cresce con il numero di acquirenti in base al loro IP. Se le chiamate partono tutte dal tuo server, Shopify vede un solo indirizzo, ed è per questo che la cache lato server non è un lusso. Il metodo per decidere quando passare a una versione più ricca lo hai nell'articolo introduttivo della serie.
Se parti da un tema Liquid
Una migrazione da tema a storefront headless si fa per gradi, non in un weekend. Lo schema più prudente è mettere la nuova storefront su un sottodominio per le sole pagine in cui il guadagno è misurabile (prodotto e collezione), lasciare il resto sul tema e confrontare dati reali di prestazioni per qualche settimana prima di spostare altro. Il checkout resta di Shopify in ogni caso. La documentazione di Hydrogen descrive la migrazione da Hydrogen legacy alla versione 2026-10, che è un'altra cosa, e non una procedura unica da Liquid a headless.
Che cosa sai fare adesso
Sai collocare Hydrogen nella versione che stai leggendo, riconoscere ciò che Shopify ti lascia in mano (routing, interfaccia) e ciò che si tiene (checkout, API), leggere un’intestazione Cache-Control con stale-while-revalidate e scegliere una strategia per tipo di dato,
e scrivere una mutazione di carrello che controlla userErrors. Nel prossimo articolo vediamo l'alternativa open source, Medusa.
Fonti
- About Hydrogen (shopify.dev) - ruolo di Hydrogen, i tre livelli, framework supportati, versione 2026-10 e legacy; consultata il 2026-11-12
- Caching Shopify API data with Hydrogen and Oxygen - strategie CacheShort, CacheLong, CacheNone, CacheCustom e relative intestazioni (pagina legacy); consultata il 2026-11-12
- About the Hydrogen cart - carrello server-side first con miglioramento progressivo; consultata il 2026-11-12
- Storefront API: cartLinesAdd - argomenti, limite di 250 righe,
userErrors; consultata il 2026-11-12 - Shopify Storefront API - token pubblico e privato, comportamento dei limiti di richieste; consultata il 2026-11-12
- RFC 5861: HTTP Cache-Control Extensions for Stale Content - definizione di stale-while-revalidate; consultata il 2026-11-12

Commenti
Caricamento commenti...
Accedi per commentare