Introduction
Modern e-commerce demands flexibility, performance, and seamless developer experience. A headless storefront—where the frontend is decoupled from the backend—empowers teams to iterate rapidly, optimize for conversions, and deliver lightning-fast user interfaces. In this guide, we’ll explore how to build a headless storefront using Next.js for your presentation layer and Shopify’s Hydrogen framework as a React-based toolkit for commerce primitives. You’ll learn how to architect your project, fetch product data via GraphQL, design dynamic pages, and optimize for performance and SEO. Whether you’re migrating from a monolith or starting fresh, by the end, you’ll have a clear roadmap and code samples to launch a scalable, future-proof storefront.

Main Body
1. Understanding Headless Commerce
Headless commerce separates the backend (product catalog, inventory, checkout) from the frontend (UI, routing, presentation) via APIs.
1.1 Benefits of Going Headless
- Developer Agility: Frontend teams choose frameworks and libraries without backend constraints.
- Omnichannel Delivery: Power websites, mobile apps, kiosks, and IoT devices from the same backend.
- Performance & SEO: Static-generation (SSG) and incremental static regeneration (ISR) deliver fast, crawlable pages.
- Scalability: Independently scale frontend and backend based on demand.
1.2 Why Next.js + Hydrogen?
- Next.js: Industry-leading React framework with SSG, SSR, API routes, and image optimizations.
- Hydrogen: Shopify’s React library offering commerce hooks (
useShopQuery
), cart state management, and UI components designed for GraphQL storefront APIs.
Analogy: Think of Next.js as the engine and chassis of a car—fast, reliable, and feature-rich. Hydrogen is the custom suspension and braking system tailored for e-commerce, ensuring you handle commerce data smoothly and securely.
2. Project Architecture & File Structure
A clear architecture keeps your code maintainable as your catalog grows.
plaintextCopyEditmy-storefront/
├── public/ # Static assets (images, favicon)
├── src/
│ ├── components/ # Reusable UI (ProductCard, Cart)
│ ├── hooks/ # Custom hooks (useCart, useShop)
│ ├── pages/ # Next.js pages (index.js, [handle].js)
│ ├── lib/ # Shopify GraphQL client setup
│ ├── styles/ # Global and module CSS
│ └── utils/ # Helpers (formatPrice, getImageUrl)
├── .env.local # API keys and secrets
├── next.config.js # Next.js configuration
├── package.json
└── hydrogen.config.js # Hydrogen-specific settings
- pages/: Next.js file-based routing drives dynamic product and collection pages.
- components/: Encapsulate UI elements for reuse and faster iteration.
- lib/: Initialize Shopify’s GraphQL client once, so all queries share configuration.
3. Setting Up Your Environment
3.1 Install Dependencies
bashCopyEditnpm init next-app my-storefront
cd my-storefront
# Install Next.js, React and Hydrogen packages
npm install react react-dom next
npm install @shopify/hydrogen @shopify/storefront-kit graphql
3.2 Configure Environment Variables
Create a .env.local
file in your project root:

envCopyEditSHOPIFY_STORE_DOMAIN=your-shop.myshopify.com
SHOPIFY_STOREFRONT_API_TOKEN=your_public_token
Load them in next.config.js
:
jsCopyEditmodule.exports = {
env: {
SHOPIFY_STORE_DOMAIN: process.env.SHOPIFY_STORE_DOMAIN,
SHOPIFY_STOREFRONT_API_TOKEN: process.env.SHOPIFY_STOREFRONT_API_TOKEN,
},
images: {
domains: ['cdn.shopify.com'],
},
};
4. Initializing the Shopify Client
In src/lib/shopify.js
:
jsCopyEditimport { ShopifyGraphQLClient } from '@shopify/hydrogen';
export const storefrontClient = new ShopifyGraphQLClient({
storeDomain: process.env.SHOPIFY_STORE_DOMAIN,
token: process.env.SHOPIFY_STOREFRONT_API_TOKEN,
});
Create a helper to run queries:
jsCopyEditexport async function shopifyQuery(query, variables = {}) {
const response = await storefrontClient.query({
data: { query, variables },
});
if (response.errors) throw new Error(response.errors[0].message);
return response.data;
}
Expert Insight: Centralizing your client ensures consistent headers, error handling, and caching strategies across the app.
5. Building Dynamic Product Pages
5.1 GraphQL Query for Product Data
Define a query in src/queries/getProductByHandle.js
:
graphqlCopyEditquery ProductByHandle($handle: String!) {
productByHandle(handle: $handle) {
id
title
description
images(first: 5) {
edges {
node {
url
altText
}
}
}
variants(first: 10) {
edges {
node {
id
title
priceV2 {
amount
currencyCode
}
}
}
}
}
}
5.2 Next.js Dynamic Route
In src/pages/products/[handle].js
:
jsxCopyEditimport React from 'react';
import { shopifyQuery } from '../../lib/shopify';
import PRODUCT_QUERY from '../../queries/getProductByHandle';
export async function getStaticPaths() {
// Fetch list of product handles
const { products } = await shopifyQuery(`{ products(first: 50) { edges { node { handle } } } }`);
const paths = products.edges.map(({ node }) => ({ params: { handle: node.handle } }));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const data = await shopifyQuery(PRODUCT_QUERY, { handle: params.handle });
return { props: { product: data.productByHandle }, revalidate: 60 };
}
export default function ProductPage({ product }) {
return (
<div>
<h1>{product.title}</h1>
<p>{product.description}</p>
{/* Map images, render variants, add AddToCart button */}
</div>
);
}
- getStaticPaths: Generates product pages at build time.
- getStaticProps + ISR: Revalidates stale pages every 60 seconds for up-to-date inventory.
6. Managing Cart State with Hydrogen Hooks
Hydrogen exposes a cart hook for easy cart management.

6.1 Creating a Cart Provider
In src/hooks/useCart.js
:
jsxCopyEditimport { createContext, useContext } from 'react';
import { CartProvider as HydrogenCartProvider, useCart } from '@shopify/hydrogen';
const CartContext = createContext();
export function CartProvider({ children }) {
const cart = useCart(); // Hydrogen hook
return <CartContext.Provider value={cart}>{children}</CartContext.Provider>;
}
export function useCartContext() {
return useContext(CartContext);
}
Wrap your app in _app.js
:
jsxCopyEditimport { CartProvider } from '../hooks/useCart';
export default function App({ Component, pageProps }) {
return (
<CartProvider>
<Component {...pageProps} />
</CartProvider>
);
}
6.2 Adding to Cart
In your ProductPage
component:
jsxCopyEditimport { useCartContext } from '../../hooks/useCart';
function AddToCartButton({ variantId }) {
const { linesAdd } = useCartContext();
return (
<button onClick={() => linesAdd([{ merchandiseId: variantId, quantity: 1 }])}>
Add to Cart
</button>
);
}
Tip: Hydrogen’s cart state persists via cookies—no extra setup needed.
7. Enhancing Performance & SEO
7.1 Image Optimization
- Use Next.js
<Image>
for lazy loading, responsive sizes, and WebP support. - Leverage Shopify’s
width
andformat
URL parameters for on-the-fly resizing.
jsxCopyEditimport Image from 'next/image';
<Image
src={image.url}
alt={image.altText}
width={800}
height={800}
priority={index === 0}
/>
7.2 Head Tags & Structured Data
In your ProductPage
:
jsxCopyEditimport Head from 'next/head';
<Head>
<title>{product.title} | My Store</title>
<meta name="description" content={product.description.substring(0, 160)} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(generateProductSchema(product)) }}
/>
</Head>
Generate JSON-LD for rich results:
jsCopyEditfunction generateProductSchema(product) {
return {
'@context': 'https://schema.org/',
'@type': 'Product',
name: product.title,
images: product.images.edges.map(e => e.node.url),
offers: {
'@type': 'Offer',
priceCurrency: product.variants.edges[0].node.priceV2.currencyCode,
price: product.variants.edges[0].node.priceV2.amount,
availability: 'https://schema.org/InStock'
}
};
}
7.3 Caching & CDN
- Deploy on Vercel or Netlify for edge caching of ISR pages.
- Use Shopify’s CDN for fast asset delivery.
Conclusion
By combining Next.js’s powerful rendering strategies with Shopify’s Hydrogen commerce primitives, you can build a headless storefront that is performant, scalable, and developer-friendly. This approach gives you full control over UI design and user experience, while relying on Shopify’s robust backend for catalog, cart, and checkout. Start by setting up your environment, establish your GraphQL client, scaffold dynamic pages, and leverage Hydrogen hooks to manage cart state. Then, optimize images, SEO tags, and deploy to an edge-optimized platform. Embrace headless commerce today to delight customers and accelerate your development workflow.