Skip to main content

Add Hydrogen to an existing project

You don't need a starter template to use Hydrogen. You can add it to a new or existing project that uses any framework that runs server code. This guide shows you how to install Hydrogen, connect it to a store, wire it into your app's request lifecycle, and show your store's products on a page.

If you want a storefront that's already built, then start from a starter template instead. Refer to Deploy a template.


In this guide, you'll learn how to do the following tasks:

  • Install Hydrogen in a project that uses the framework of your choice.
  • Configure environment variables for mock.shop or for your own store.
  • Wire the Storefront API client, request handlers, and customer accounts into your app.
  • Show your store's products on a page, with a link to buy each one.
  • Check that your storefront works.

  • Install Node.js version 22 or 24.
  • Use a JavaScript project with a package.json file at its root, built with a framework that runs server code, like route handlers, middleware, loaders, or server functions. Hydrogen runs before your router on each request, so apps that are browser-only or configured only for static output don't work.
  • To connect your own store, get Storefront API access through the Headless channel or the Hydrogen channel. You don't need a store to start building with mock.shop.

Anchor to Step 1: Install HydrogenStep 1: Install Hydrogen

Create a project with your framework's CLI, like npx create-next-app@latest or npx create-react-router@latest, or use a project you already have. Then run the Hydrogen setup command from the project's root directory:

Terminal

npx @shopify/hydrogen@latest setup

The setup command does the following tasks:

  • Installs @shopify/hydrogen with your project's package manager, which it detects from your lockfile. If @shopify/hydrogen is already in your package.json file but isn't installed, then it runs your package manager's install command instead.
  • Adds agent instructions that match the installed Hydrogen version to your project's .claude and .agents directories.

The setup command doesn't change your app's code. You wire Hydrogen into your app in Step 3 and Step 4. You can follow those steps yourself, or open the project in your coding agent and ask it to follow them for you:

Prompt

Set up my store with Shopify.

The agent works through the same steps in the same order, and checks each one before it starts the next.


Anchor to Step 2: Configure environment variablesStep 2: Configure environment variables

Hydrogen reads its store connection from environment variables. Until you add a private Storefront API token, you can build against mock.shop, a public Storefront API with demo data that doesn't need a token.

Use the variable names in this step throughout your app. In Next.js, replace PUBLIC_ with NEXT_PUBLIC_, and use NEXT_PUBLIC_SHOP_ID.

mock.shop is a catalog of demo stores, each on its own host. The store at mock.shop sells apparel. For other catalogs, refer to the directory at mock.shop/llms.txt. To build against the demo catalog that's closest to your store, set your store domain to its host and leave the tokens out:

mock.shop connection

.env
PUBLIC_STORE_DOMAIN=pets.mock.shop
# PUBLIC_STOREFRONT_API_TOKEN=

Keep the public Storefront API token commented out until you have one. An empty assignment, like PUBLIC_STOREFRONT_API_TOKEN=, loads as an empty string, and the Storefront API client rejects empty tokens. A missing token means tokenless access.

Connect a real store to test real checkout and customer accounts.

Get your store domain and tokens from the Headless or Hydrogen channel in the Shopify admin, then add them to your project's .env file.

The private Storefront API token is server-only. Never expose it to the browser, and never give it a browser prefix like NEXT_PUBLIC_.

Store connection

.env
# Server-only
PRIVATE_STOREFRONT_API_TOKEN=<your private Storefront API token>
SESSION_SECRET=<a random string of 32 or more characters>

# Browser-safe
PUBLIC_STORE_DOMAIN=your-shop.myshopify.com
# PUBLIC_STOREFRONT_API_TOKEN=
SHOP_ID=<your numeric shop ID>
PUBLIC_STOREFRONT_ID=<your storefront ID>
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID=<your Customer Account API client ID>

If you deploy to Oxygen and your storefront is linked to the Hydrogen channel, then you can pull these values with Shopify CLI instead of copying them by hand:

Terminal

npx shopify hydrogen link
npx shopify hydrogen env pull

The following table describes each variable:

VariableRequiredDescription
PUBLIC_STORE_DOMAINYesYour store's myshopify.com domain, or a mock.shop host.
PRIVATE_STOREFRONT_API_TOKENFor a private clientThe private Storefront API token. Server-only.
PUBLIC_STOREFRONT_API_TOKENNoThe public Storefront API token, for browser-safe clients. Leave it commented out until you have a token.
SHOP_IDFor customer accountsThe numeric shop ID for customer accounts and Shopify's runtime scripts.
PUBLIC_STOREFRONT_IDFor analyticsThe Hydrogen storefront ID. Use 0 if your storefront doesn't have one.
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_IDFor customer accountsThe Customer Account API client ID.
SESSION_SECRETFor customer accountsA random secret, 32 characters or longer, that signs the session cookie.
PUBLIC_CHECKOUT_DOMAINNoYour checkout domain, for app-level configuration like a Content Security Policy. Checkout links come from cart data, like cart.checkoutUrl, not from this variable.

To generate a session secret, run the following command:

Terminal

node -e "console.log(crypto.randomBytes(32).toString('base64url'))"

Anchor to Step 3: Wire Hydrogen into your appStep 3: Wire Hydrogen into your app

This step connects Hydrogen to your app's request lifecycle. Complete each part in order, and check it before you start the next. After each part that changes code, run your app's typecheck script and fix any errors. Type errors compound, so they're easier to fix one part at a time.

Anchor to Keep environment access on the serverKeep environment access on the server

Read environment variables only in server code. Never read process.env, import.meta.env, or your framework's environment modules from client components or from modules that client components import.

Resolve values that come from environment variables on the server, and pass them to client components as props or loader data. For example, build the shop object that ShopifyScripts needs on the server. Browser code calls Shopify through Hydrogen's same-origin routes, so private tokens never leave the server.

For which files run on the server in React Router and Next.js, refer to Framework wiring.

Before you continue, confirm that no client component, and no module that a client component imports, reads environment variables.

Anchor to Set up route templatesSet up route templates

Hydrogen builds links to products, collections, and other Shopify resources from a shared route manifest. If your app already has routes for these resources, then keep them. Otherwise, use the standard paths:

PagePath
Collection listing/collections
Collection/collections/{handle}
Search results/search
Product/products/{handle}
Cart/cart

Use the plural /products, not /product. Some paths, like /api/cart, /checkout, and /cart/{variantId}:{quantity}, belong to Hydrogen's request handlers, so don't create pages for them.

To create the manifest, follow Configure route templates.

Before you continue, confirm that your route templates module exports your manifest. You check that Shopify's standard URLs reach your pages after you wire request handlers.

Anchor to Create the Storefront API clientCreate the Storefront API client

Create the Storefront API client for your framework by following Query the Storefront API. Then add hydrogen gql check to your existing typecheck script, so that the script validates your queries against the Storefront API schema. Run it after TypeScript and any route type generation:

Add GraphQL validation to typecheck

package.json

{
"scripts": {
"typecheck": "react-router typegen && tsc && hydrogen gql check --fail-on-warn"
}
}
{
"scripts": {
"typecheck": "next typegen && tsc --noEmit && hydrogen gql check --fail-on-warn"
}
}

If you use mock.shop, then create a public client without a token, because mock.shop rejects private tokens. Refer to Use mock.shop during development.

For more information about query validation, refer to Type and validate Storefront API queries.

Before you continue, confirm that typecheck passes. You run your first query in Step 4.

Anchor to Wire request handlersWire request handlers

Run Hydrogen's request handlers before your router, and check for Shopify redirects after your router returns a 404. Follow Wire Hydrogen request handlers.

Then make the Storefront API client available to your pages:

Store the Storefront API client in router context from your root route's middleware, so that your loaders can read it:

Share the client with your loaders

app/lib/context.ts

import type {StorefrontClient} from '@shopify/hydrogen';
import {createContext} from 'react-router';

export const storefrontContext = createContext<StorefrontClient>();
import type {Route} from './+types/root';
import {storefrontContext} from './lib/context';
import {handleRequest} from './lib/shopify';

export const middleware: Route.MiddlewareFunction[] = [
({request, context}, next) =>
handleRequest(request, async (_request, storefrontClient) => {
context.set(storefrontContext, storefrontClient);
return next();
}),
];

For the full wiring in each framework, refer to Framework wiring.

Before you continue, confirm that typecheck passes. You check the request handlers in Step 4.

Anchor to Set up customer accountsSet up customer accounts

Customer accounts need a real store. If you're using mock.shop, then skip this part until you connect your store.

Create the customer session, and register the account handlers with your request handlers. Follow Steps 1 to 3 of Set up customer accounts.

Before you continue, confirm that typecheck passes.


Anchor to Step 4: Show products on a pageStep 4: Show products on a page

Add a page that queries the first few products in your store and lists them. Each product shows its title, its price, and a Buy now link. The page is the first place your app runs a Storefront API query, so it shows that your client, your request handlers, and your store connection work together.

List products

app/routes/home.tsx
import {formatMoney, gql} from '@shopify/hydrogen';

import {storefrontContext} from '~/lib/context';
import type {Route} from './+types/home';

const PRODUCTS_QUERY = gql(`
query Products {
products(first: 8) {
nodes {
id
title
priceRange {
minVariantPrice {
amount
currencyCode
}
}
variants(first: 1) {
nodes {
id
}
}
}
}
}
`);

export async function loader({context}: Route.LoaderArgs) {
const storefront = context.get(storefrontContext);
const {data} = await storefront.graphql(PRODUCTS_QUERY);

return {products: data?.products.nodes ?? []};
}

export default function Home({loaderData}: Route.ComponentProps) {
return (
<main>
<h1>Products</h1>
<ul>
{loaderData.products.map((product) => {
const variantId = product.variants.nodes[0]?.id.split('/').pop();

return (
<li key={product.id}>
<h2>{product.title}</h2>
<p>
{formatMoney(product.priceRange.minVariantPrice, {
locale: 'en-US',
}).toString()}
</p>
{variantId ? <a href={`/cart/${variantId}:1`}>Buy now</a> : null}
</li>
);
})}
</ul>
</main>
);
}

The loader reads the client from the router context that your middleware sets in Wire request handlers.

Keep the following in mind:

  • formatMoney() formats the amount and currency that Shopify returns. Pass your customer's locale instead of en-US when your storefront supports more than one.
  • The Buy now link uses Shopify's cart permalink format, /cart/{variantId}:{quantity}, with the number at the end of the variant ID. Hydrogen's request handlers answer it and redirect the customer to your store's checkout with that product in the cart, so you don't need a page for it.

Start your framework's development server, like npm run dev, and open the local URL that it prints. Then confirm the following:

  • Your home page lists products from your store or from mock.shop, each with a price.
  • typecheck passes, and your editor shows the query result's fields as typed.
  • /graphiql returns a 200 response.
  • If you connected your own store, then Buy now opens your store's checkout, and /admin redirects to the Shopify admin. Buy now and /admin don't work with mock.shop.
  • If you set up HTTPS and customer accounts, then /account/login redirects to Shopify's OAuth /oauth/authorize endpoint.

Customer accounts need HTTPS. To test sign-in locally, refer to Test sign-in locally with HTTPS.


Anchor to Step 5: Check your storefrontStep 5: Check your storefront

Run your app's own checks. Look in your package.json file for the following scripts, and run the ones that exist, in this order:

  1. format, to fix formatting.
  2. lint, typecheck, or check. Confirm that typecheck still runs hydrogen gql check before you trust a passing result.
  3. build.
  4. test. If your project uses Playwright, then run it headless.
  5. format:check, if it's separate from format.

Run the checks again after every code change. A check that passed before your last change doesn't count.

Then test your storefront while it runs. Work through the production checklist against your development server, and against a production build if your framework can run one locally.


Build the rest of your storefront's features in the following order. Later features build on earlier ones. For example, the cart drawer reuses your cart page's line items, and analytics events depend on Shopify's runtime scripts.

OrderFeatureGuide
1A home page that lists collections and products, built from the page in Step 4Query the Storefront API
2Collection and search pages, with filters that sync with the URLBuild collection and search pages
3Product pages, with a variant form and add to cartBuild product pages with variant selection
4A /cart page that works without JavaScript, so you can check that items you add from a product page appear in the cartBuild cart UI
5Shopify's runtime scripts, and your framework's navigation hookAdd analytics and consent
6A cart drawer in your root layout, and a navbar with a cart triggerBuild a cart drawer
7An account page with the customer's profile and a sign-out button, and an account link in the navbarSet up customer accounts
8Analytics events, sent only when the customer's consent allows themAdd analytics and consent

Build your storefront's UI with the styling approach that your app already uses, like Tailwind CSS or CSS Modules.

Your app also owns image optimization and SEO. Use your framework's image component, or <img srcset> with Shopify CDN URLs that set sizing parameters from ImageTransformInput. Render meta tags, Open Graph tags, and structured data with your framework's metadata API and Storefront API data.

After you add each feature, run the checks in Step 5 again. When your storefront is ready, deploy it:


Was this page helpful?