Skip to main content

Hydrogen

Hydrogen is Shopify's SDK for building storefronts in the JavaScript framework of your choice. It includes a typed Storefront API client, request handlers, and tools for cart, products, collections, search, customer accounts, analytics, and Shop Pay, with React and Vue bindings.

Hydrogen works in a server-rendered JavaScript storefront built with the framework of your choice. You need Storefront API access for the store you want to connect.

Create a project with your framework, then run the setup command in the project folder. If you run the command interactively in an empty folder instead, it offers to create a storefront from the Hydrogen template.

You can also use a template to deploy a full storefront using either React Router or Next.js. To learn more, refer to Get started with Hydrogen.

Add Hydrogen to a project

npx create-react-router@latest my-store
cd my-store
npx @shopify/hydrogen@latest setup

Hydrogen runs on both sides of your storefront. On the server, the Storefront API client queries Shopify, and request handling serves Shopify's routes, like the cart endpoint, before your framework's router.

In the browser, stores hold the state that changes while a customer is on the page, like the cart. If you're building with React or Vue, bindings connect the stores to your components.

The two sides connect through the routes that handleShopifyRoutes serves.

Your code imports Hydrogen at runtime from the following entry points:

Entry pointRuns inContains
@shopify/hydrogenServer and browserThe Storefront API client, request handling, server handlers, core stores, and utilities.
@shopify/hydrogen/reactBrowser and server renderingThe React bindings.
@shopify/hydrogen/vueBrowser and server renderingThe Vue bindings.
@shopify/hydrogen/customer-accountServerCustomer account login, sessions, errors, and the Customer Account API client.
@shopify/hydrogen/viteViteThe local HTTPS plugin.

Anchor to Query the Storefront APIQuery the Storefront API

Create a Storefront API client for each request on the server, then query the client with gql documents. The request context passes the customer's cookies, IP address, and declared $country and $language localization variables to the Storefront API. Each query returns data typed against the Storefront API schema.

Every request runs through Hydrogen's request handling before it reaches your router.

Query the Storefront API

import {
createShopifyRequestContext,
createStorefrontClient,
gql,
} from '@shopify/hydrogen';

const {PUBLIC_STORE_DOMAIN, PRIVATE_STOREFRONT_API_TOKEN} = process.env;
// Oxygen sets this header. Other hosts use their own trusted header.
const buyerIp = request.headers.get('oxygen-buyer-ip');

if (!PUBLIC_STORE_DOMAIN || !PRIVATE_STOREFRONT_API_TOKEN || !buyerIp) {
throw new Error('Missing the store domain, private token, or buyer IP');
}

const requestContext = createShopifyRequestContext({
request,
i18n: {language: 'EN', country: 'US'},
buyerIp,
});

const storefront = createStorefrontClient({
type: 'private',
requestContext,
config: {
storeDomain: PUBLIC_STORE_DOMAIN,
privateStorefrontToken: PRIVATE_STOREFRONT_API_TOKEN,
},
});

const {data} = await storefront.graphql(
gql(`
query Home {
products(first: 3) {
nodes { handle title }
}
}
`),
);

Anchor to Query the Customer Account APIQuery the Customer Account API

The Customer Account API serves the signed-in customer's orders, profile, and addresses. Customers sign in with Shopify customer accounts, and each Customer Account API request sends the customer's access token.

Create one session with createCustomerSession at module scope, and pass the session to createCustomerAccountServerHandlers. The handlers serve the login, authorize, refresh, and logout routes under /account, and keep the cart's buyer identity matched to the signed-in customer.

In an account page, read the access token with getAccessToken, and query with a Customer Account API client. When the session has no access token, send the customer to the login route.

Query the Customer Account API

import {
createCartServerHandlers,
type ShopifyRequestContext,
} from '@shopify/hydrogen';
import {
createCustomerAccountClient,
createCustomerAccountServerHandlers,
createCustomerSession,
gql,
type ReadonlyCustomerSessionManager,
} from '@shopify/hydrogen/customer-account';

const {SHOP_ID, PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID} = process.env;

if (!SHOP_ID || !PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID) {
throw new Error('Missing the shop ID or Customer Account API client ID');
}

export const customerSession = createCustomerSession({
shopId: SHOP_ID,
customerAccountApiClientId: PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID,
});

// Add customerAccountHandlers next to cartHandlers in handleShopifyRoutes
export const cartHandlers = createCartServerHandlers({customerSession});
export const customerAccountHandlers = createCustomerAccountServerHandlers({
customerSession,
cartServerHandlers: cartHandlers,
});

export async function loadAccount(
sessionManager: ReadonlyCustomerSessionManager,
requestContext: ShopifyRequestContext,
) {
const accessToken = await customerSession.getAccessToken(
sessionManager,
requestContext,
);

if (!accessToken) {
return {redirect: '/account/login?return_to=/account'};
}

const customerAccount = createCustomerAccountClient({
shopId: SHOP_ID,
requestContext,
});

return customerAccount.graphql(
gql(`
query Account {
customer {
firstName
orders(first: 3) {
nodes { id }
}
}
}
`),
{accessToken},
);
}

Hydrogen's request handling runs around your framework's router. Before your router, handleShopifyRoutes serves the routes that every Shopify storefront needs, like checkout and your cart handlers. After your router, handleShopifyRedirects checks the store's URL redirects for any page that returns a 404.

Handle a request

import {
createShopifyRequestContext,
createStorefrontClient,
handleShopifyRedirects,
handleShopifyRoutes,
} from '@shopify/hydrogen';
import {appRouter} from './app-router';
import {cartHandlers} from './cart-handlers';
import {routeTemplates} from './route-templates';
import {createSessionManager} from './session';

export async function handleRequest(
request: Request,
env: {PUBLIC_STORE_DOMAIN: string},
) {
const requestContext = createShopifyRequestContext({
request,
i18n: {language: 'EN', country: 'US'},
});
const storefront = createStorefrontClient({
type: 'public',
requestContext,
config: {storeDomain: env.PUBLIC_STORE_DOMAIN},
});
const sessionManager = await createSessionManager(request);

// Shopify routes, like /api/cart and checkout, run before your router
const shopifyRoute = handleShopifyRoutes({
request,
requestContext,
storefrontClient: storefront,
sessionManager,
routeTemplates,
handlers: [cartHandlers],
});
if (shopifyRoute) return shopifyRoute;

// Your router renders pages, and its loaders query the Storefront API
const response = await appRouter(request, {storefront});

// Store URL redirects run only when your router doesn't match
if (response.status === 404) {
const redirect = await handleShopifyRedirects({
request,
storefrontClient: storefront,
routeTemplates,
});
if (redirect) return redirect;
}

requestContext.applyResponseHeaders(response.headers);
return response;
}

Anchor to Serve the cart endpointServe the cart endpoint

createCartServerHandlers() returns the handlers for the /api/cart route. They read the cart ID from a cookie, run the matching cart query or mutation, and return JSON to the cart store. When a form is submitted without JavaScript, they redirect back to the page that sent it.

To register the handlers, pass them to handleShopifyRoutes in the handlers array as shown in the server.ts example.

The handlers carry your cart query's types to the browser.

Serve the cart endpoint

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

export const cartHandlers = createCartServerHandlers();
import {handleShopifyRoutes} from '@shopify/hydrogen';
import {cartHandlers} from './cart-handlers';

// In your request handler, before your router
const shopifyRoute = handleShopifyRoutes({
request,
requestContext,
storefrontClient: storefront,
sessionManager,
handlers: [cartHandlers],
});
if (shopifyRoute) return shopifyRoute;

Anchor to Change the cart with cart formsChange the cart with cart forms

You change the cart by submitting a form. Each form has an intent, like add, remove, or set, and the fields that the intent needs, like a variant ID or a cart line ID.

The cart store handles the submission, updates the cart state right away, and sends the change through Standard Actions, which ShopifyScripts loads, to your cart endpoint.

Add a variant to the cart

// <form id="add-to-cart" method="post" action="/api/cart">
// <input type="hidden" name="merchandiseId" value="gid://shopify/ProductVariant/1">
// <button type="submit" name="intent" value="add">Add to cart</button>
// </form>
const form = document.querySelector('#add-to-cart');

form.addEventListener('submit', (event) => {
event.preventDefault();
cart.handleFormSubmit(event);
});
import {useCartForm} from './cart';

export function AddToCart({variantId}: {variantId: string}) {
const {formProps, register} = useCartForm();

return (
<form {...formProps()}>
<input type="hidden" {...register('merchandiseId', {value: variantId})} />
<button type="submit" {...register('add')}>
Add to cart
</button>
</form>
);
}
<script setup lang="ts">
import {useCartForm} from './cart';

defineProps<{variantId: string}>();

const {formProps, register} = useCartForm();
</script>

<template>
<form v-bind="formProps()">
<input type="hidden" v-bind="register('merchandiseId', {value: variantId})" />
<button type="submit" v-bind="register('add')">Add to cart</button>
</form>
</template>

If you're building with React or Vue, you can use bindings to wrap the core stores in providers, components, and framework-native state.

For React, you pass a hook a selector function like (state) => state.data.totalQuantity, and the component re-renders only when that value changes.

For Vue, you pass a composable a selector function, and it returns a ref (Vue's reactive value) that updates only when the selected value changes.

Frameworks without bindings use the core stores directly. In the browser, load Shopify's storefront scripts, then call connect() so the store listens for Standard Actions cart events. Any script on the page can change the cart through these events.

Show the cart count

import {configureCartEndpoint, createCartStore} from '@shopify/hydrogen';

configureCartEndpoint('/api/cart');
const cart = createCartStore({initialData});

cart.connect();

cart.subscribe((state) => {
document.querySelector('#cart-count').textContent = String(
state.data.totalQuantity,
);
});
// cart.ts
import {createCartComponents} from '@shopify/hydrogen/react';
import type {cartHandlers} from './cart-handlers';

export const {CartProvider, useCart, useCartForm} =
createCartComponents<typeof cartHandlers>();

// App.tsx
import type {CartDataFromHandlers} from '@shopify/hydrogen';
import {CartProvider, useCart} from './cart';
import type {cartHandlers} from './cart-handlers';

function CartCount() {
const totalQuantity = useCart((state) => state.data.totalQuantity);
return <span>{totalQuantity}</span>;
}

export function App({
initialCart,
}: {
initialCart: CartDataFromHandlers<typeof cartHandlers> | null;
}) {
return (
<CartProvider initialData={{cart: initialCart}}>
<CartCount />
</CartProvider>
);
}
<!-- CartCount.vue -->
<script setup lang="ts">
// cart.ts calls createCartComponents from @shopify/hydrogen/vue
import {useCart} from './cart';

const totalQuantity = useCart((state) => state.data.totalQuantity);
</script>

<template>
<span>{{ totalQuantity }}</span>
</template>

<!-- App.vue -->
<script setup lang="ts">
import type {CartDataFromHandlers} from '@shopify/hydrogen';
import {CartProvider} from './cart';
import type {cartHandlers} from './cart-handlers';
import CartCount from './CartCount.vue';

defineProps<{initialCart: CartDataFromHandlers<typeof cartHandlers> | null}>();
</script>

<template>
<CartProvider :initial-data="{cart: initialCart}">
<CartCount />
</CartProvider>
</template>



Was this page helpful?