Headless E-commerce: Why Shopify + Next.js is the Winning Combo
The e-commerce world has a speed problem. Studies consistently show that every 100ms of latency costs conversions. Amazon famously found that a 1-second delay could cost 1.6 billion dollars in annual sales.
Yet most e-commerce platforms are slow. They load bloated themes, render everything on the server, and make customers wait while the page assembles itself.
Headless commerce changes this equation entirely. By separating the storefront (what customers see) from the backend (where products, orders, and inventory live), you get the best of both worlds: the reliability of established platforms with the performance of modern web frameworks.
And the most powerful combination? Shopify as the backend, Next.js as the frontend.
What Is Headless Commerce?
Traditional e-commerce platforms are "monolithic." The same system handles both the administrative backend and the customer-facing storefront. Shopify, BigCommerce, and Magento all started this way.
Monolithic architecture:
┌───────────────────────────────────────────┐
│ Shopify Platform │
│ ┌─────────────────────────────────────┐ │
│ │ Admin Dashboard │ │
│ │ • Product management │ │
│ │ • Order management │ │
│ │ • Inventory tracking │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ Storefront (Liquid themes) │ │
│ │ • Product pages │ │
│ │ • Cart and checkout │ │
│ │ • Customer accounts │ │
│ └─────────────────────────────────────┘ │
└───────────────────────────────────────────┘
In headless architecture, these layers separate:
Headless architecture:
┌─────────────────────────────────────┐
│ Custom Frontend (Next.js) │
│ • Product pages │
│ • Cart experience │
│ • Custom checkout UI │
│ • Complete design freedom │
└─────────────────┬───────────────────┘
│ Storefront API
▼
┌─────────────────────────────────────┐
│ Shopify Backend (Headless) │
│ • Product catalog │
│ • Inventory management │
│ • Order processing │
│ • Payment handling │
│ • Admin dashboard │
└─────────────────────────────────────┘
The frontend talks to Shopify through APIs (GraphQL Storefront API), fetching only the data it needs and rendering it however you want.
Why This Architecture Wins
Performance That Converts
Next.js enables performance patterns impossible with traditional themes:
Static Generation (SSG): Product pages can be pre-rendered at build time. When a customer visits, they get HTML instantly. No server rendering, no API calls, no waiting.
// pages/products/[handle].tsx
export async function generateStaticParams() {
const products = await shopify.getProducts();
return products.map(p => ({ handle: p.handle }));
}
export default async function ProductPage({ params }) {
// This runs at build time, not request time
const product = await shopify.getProduct(params.handle);
return <ProductDetail product={product} />;
}
Incremental Static Regeneration (ISR): Pages can regenerate in the background without full rebuilds:
export const revalidate = 60; // Regenerate every 60 seconds
export default async function ProductPage({ params }) {
const product = await shopify.getProduct(params.handle);
// Fresh data every minute, instant delivery always
}
Real-world impact:
- LCP under 1 second (vs. 3-5 seconds for typical themes)
- Time to Interactive under 2 seconds
- Core Web Vitals scores consistently green
Unlimited Customization
Shopify themes are limited by Liquid templating and the theme architecture. Want to:
- Add a 3D product configurator?
- Build an augmented reality try-on feature?
- Create a unique cart experience?
- Integrate with headless CMS for content?
With traditional themes, you're fighting the platform. With headless, you have complete control.
// Any React component library works
import { Canvas } from '@react-three/fiber';
import { useGLTF } from '@react-three/drei';
function Product3DViewer({ modelUrl }) {
const { scene } = useGLTF(modelUrl);
return (
<Canvas>
<ambientLight intensity={0.5} />
<primitive object={scene} />
<OrbitControls />
</Canvas>
);
}
Omnichannel Ready
Your Shopify backend serves multiple frontends simultaneously:
- Web storefront (Next.js)
- Mobile app (React Native)
- In-store kiosks (Custom React app)
- Social commerce (Direct API integration)
- B2B portal (Different UI, same backend)
One source of truth for inventory and orders, multiple customer touchpoints.
Developer Experience
For developers, headless is liberating:
- Use any framework or library
- Modern tooling (TypeScript, ESLint, Prettier)
- Component-based architecture
- Automated testing
- CI/CD pipelines
- Local development that actually works
Setting Up Shopify + Next.js
Let's walk through the key setup steps.
1. Enable Headless Access
In Shopify Admin:
-
Go to Apps → Develop apps
-
Create a new app
-
Configure Storefront API scopes:
unauthenticated_read_product_listingsunauthenticated_read_product_inventoryunauthenticated_read_product_tagsunauthenticated_write_checkoutsunauthenticated_read_checkouts
-
Generate a Storefront API access token
2. Create Next.js Project
npx create-next-app@latest my-store --typescript
cd my-store
Install the Shopify client:
npm install @shopify/hydrogen-react
# or use the lightweight client
npm install shopify-buy
3. Configure the Shopify Client
// lib/shopify.ts
import { createStorefrontClient } from '@shopify/hydrogen-react';
const client = createStorefrontClient({
storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
publicStorefrontToken: process.env.SHOPIFY_STOREFRONT_TOKEN!,
});
export async function getProducts() {
const query = `
query Products {
products(first: 50) {
edges {
node {
id
title
handle
description
priceRange {
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
edges {
node {
url
altText
}
}
}
}
}
}
}
`;
const response = await client.query(query);
return response.data.products.edges.map(edge => edge.node);
}
export async function getProduct(handle: string) {
const query = `
query Product($handle: String!) {
productByHandle(handle: $handle) {
id
title
description
variants(first: 100) {
edges {
node {
id
title
priceV2 {
amount
currencyCode
}
availableForSale
}
}
}
images(first: 10) {
edges {
node {
url
altText
width
height
}
}
}
}
}
`;
const response = await client.query(query, { handle });
return response.data.productByHandle;
}
4. Build the Cart
Cart state management is crucial. Here's a pattern using React Context:
// context/cart-context.tsx
'use client';
import { createContext, useContext, useState, useCallback } from 'react';
import { createCart, addToCart, updateCart } from '@/lib/shopify';
interface CartContextType {
cart: Cart | null;
loading: boolean;
addItem: (variantId: string, quantity: number) => Promise<void>;
updateItem: (lineId: string, quantity: number) => Promise<void>;
removeItem: (lineId: string) => Promise<void>;
}
const CartContext = createContext<CartContextType | null>(null);
export function CartProvider({ children }) {
const [cart, setCart] = useState<Cart | null>(null);
const [loading, setLoading] = useState(false);
const addItem = useCallback(async (variantId: string, quantity: number) => {
setLoading(true);
try {
if (!cart) {
// Create new cart
const newCart = await createCart(variantId, quantity);
setCart(newCart);
// Store cart ID in localStorage for persistence
localStorage.setItem('cartId', newCart.id);
} else {
// Add to existing cart
const updatedCart = await addToCart(cart.id, variantId, quantity);
setCart(updatedCart);
}
} finally {
setLoading(false);
}
}, [cart]);
// ... updateItem, removeItem implementations
return (
<CartContext.Provider value={{ cart, loading, addItem, updateItem, removeItem }}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => {
const context = useContext(CartContext);
if (!context) throw new Error('useCart must be used within CartProvider');
return context;
};
5. Checkout Integration
Shopify handles checkout. You redirect customers to the secure Shopify checkout:
// components/checkout-button.tsx
'use client';
import { useCart } from '@/context/cart-context';
export function CheckoutButton() {
const { cart } = useCart();
if (!cart || cart.lines.length === 0) {
return (
<button disabled className="opacity-50 cursor-not-allowed">
Cart is empty
</button>
);
}
return (
<a
href={cart.checkoutUrl}
className="w-full bg-black text-white py-3 rounded-lg"
>
Checkout - {formatPrice(cart.totalAmount)}
</a>
);
}
Shopify Checkout handles:
- Payment processing (all major providers)
- Tax calculation
- Shipping rates
- Discount codes
- Order creation
You get PCI compliance without building it yourself.
Advanced Patterns
Search with Algolia or Typesense
Shopify's search is limited. For serious stores, integrate a dedicated search service:
// Using Algolia
import algoliasearch from 'algoliasearch/lite';
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch';
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!
);
export function ProductSearch() {
return (
<InstantSearch searchClient={searchClient} indexName="products">
<SearchBox />
<Hits hitComponent={ProductHit} />
</InstantSearch>
);
}
Sync products to Algolia via Shopify webhooks or scheduled jobs.
Inventory-Aware UI
Real-time inventory prevents overselling and creates urgency:
// components/product-stock.tsx
'use client';
import { useEffect, useState } from 'react';
import { getInventoryLevel } from '@/lib/shopify';
export function StockIndicator({ variantId }) {
const [stock, setStock] = useState<number | null>(null);
useEffect(() => {
// Fetch fresh inventory (not cached)
getInventoryLevel(variantId).then(setStock);
}, [variantId]);
if (stock === null) return null;
if (stock === 0) {
return <span className="text-red-600">Out of Stock</span>;
}
if (stock < 10) {
return <span className="text-orange-600">Only {stock} left</span>;
}
return <span className="text-green-600">In Stock</span>;
}
Personalization
Without theme restrictions, you can personalize freely:
// Personalized recommendations
export async function getRecommendations(productId: string, customerId?: string) {
// Fetch from Shopify
const shopifyRecs = await shopify.getProductRecommendations(productId);
if (customerId) {
// Enhance with personalization service
const personalRecs = await personalizationAPI.getRecommendations({
productId,
customerId,
count: 8,
});
return mergeRecommendations(shopifyRecs, personalRecs);
}
return shopifyRecs;
}
Multi-Currency and Localization
Shopify Markets + Next.js i18n:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US';
// Set currency/locale based on region
const response = NextResponse.next();
response.cookies.set('country', country);
return response;
}
// Use in components
export function PriceDisplay({ amount, currencyCode }) {
const country = useCountry();
const localizedPrice = convertCurrency(amount, currencyCode, country);
return <span>{formatCurrency(localizedPrice, country)}</span>;
}
When to Go Headless (And When Not To)
Go Headless If:
- Performance is critical: Your analytics show high bounce rates on slow pages
- You need custom experiences: 3D viewers, configurators, unique checkout flows
- You're building omnichannel: Multiple storefronts from one backend
- Your dev team prefers React: Liquid templating is limiting
- You're scaling: Need architecture that handles traffic spikes
Stay With Themes If:
- Budget is tight: Themes are faster to launch
- Content changes frequently: Merchants editing Liquid is simpler than deploys
- You're testing product-market fit: Don't over-engineer early
- Team doesn't have React expertise: Headless requires frontend skills
The Hybrid Approach
You can go headless incrementally:
- Keep most of the store on themes
- Build specific high-impact pages with Next.js (PDP, landing pages)
- Gradually migrate as you prove value
Performance Benchmarks
What you can expect from well-built headless stores:
| Metric | Theme-Based | Headless (Next.js) | |--------|-------------|-------------------| | LCP | 2.5-4.0s | 0.8-1.5s | | FID/INP | 150-300ms | 50-100ms | | CLS | 0.1-0.3 | under 0.05 | | Lighthouse Score | 40-70 | 90-100 | | Time to First Byte | 500-1500ms | 50-200ms |
These numbers translate directly to conversion rate improvements.
Ready to Go Headless?
Headless commerce with Shopify and Next.js is the modern standard for serious e-commerce brands. Better performance, unlimited customization, and a development experience that lets you move fast.
At High Mountain Studio, we've built headless storefronts for brands that demand the best user experience. From initial architecture to production deployment, we handle the technical complexity so you can focus on your products.


