Headless Shopify with Next.js: collections, products, cart and orders via the API
Keep Shopify for products, stock and payment, and build a custom store in Next.js. Architecture, Storefront API, cart, order creation, SEO and costs.
By Léo Chuzel · Published on · 5 min read
Shopify themes suit the vast majority of stores. But some brands run into their limits: a very specific design, performance to push to the max, a store that has to coexist with a content site, a configurator or a custom customer area. The solution is called headless Shopify: you keep Shopify for everything it does very well, and build the storefront with a modern framework like Next.js. Here’s how it works in practice.
What is headless?
A classic Shopify store is a back office (products, stock, orders, customers) plus a “head”: the theme that displays the store. In headless, you remove that head. Shopify becomes an e-commerce engine accessible via API, and the website customers see is an independent application, custom-built and hosted separately.
- Shopify handles: the catalogue, variants, stock, prices and currencies, promotions, payment, orders, shipping, the back office.
- Next.js handles: page rendering, navigation, design, editorial content, performance and SEO.
- The API links the two: Shopify’s Storefront API, in GraphQL, provides product data and manages the cart.
The typical architecture
- Next.js generates the collection and product pages statically, from Storefront API data.
- A CMS (for example Payload, Sanity or Shopify metafields) manages editorial content: homepage, lookbooks, blog. See our article on headless CMSs.
- The cart is created and updated via Shopify’s Cart API, from the browser or from the server.
- Payment happens on Shopify checkout, secure and optimised, or via an order created through the Admin API in some cases.
- Shopify webhooks notify the website when a product, price or stock level changes, so only the affected pages are regenerated.
Displaying collections and products
The Storefront API is queried in GraphQL with a public access token, created from Shopify’s “Headless” channel. You request exactly the fields you need, nothing more. Example for a collection page:
query Collection($handle: String!) {
collection(handle: $handle) {
title
description
seo { title description }
products(first: 24) {
nodes {
handle
title
featuredImage { url altText width height }
priceRange {
minVariantPrice { amount currencyCode }
}
}
}
}
}On the Next.js side, the call is made from a server component. The data is cached and associated with a tag, which allows pages to be regenerated on demand when Shopify reports a change:
export async function shopifyFetch(query, variables, tags = []) {
const res = await fetch(process.env.SHOPIFY_STOREFRONT_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_TOKEN,
},
body: JSON.stringify({ query, variables }),
next: { tags },
});
const { data, errors } = await res.json();
if (errors) throw new Error(errors[0].message);
return data;
}Product pages follow the same principle: title, description, images, variants (size, colour) with their availability, metafields for additional information (composition, size guide, care instructions). Filtering and sorting collections is also available in the API.
Managing the cart
The cart is managed by Shopify, not by your website: that’s what guarantees prices, promotions and stock are always correct. You create a cart on the first add, keep its ID in a cookie, then add, update or remove lines.
mutation CreateCart($lines: [CartLineInput!]!) {
cartCreate(input: { lines: $lines }) {
cart {
id
checkoutUrl
totalQuantity
cost {
totalAmount { amount currencyCode }
}
}
userErrors { field message }
}
}The cartLinesAdd, cartLinesUpdate and cartLinesRemove mutations handle the rest. Discount codes, order notes and custom attributes (engraving, gift message) also go through the cart. With Next.js Server Actions, these calls stay server-side and the token is never needlessly exposed.
Creating the order
The standard route: Shopify checkout
Every cart has a checkoutUrl. When it’s time to pay, the customer is redirected to Shopify checkout, which handles addresses, shipping costs, taxes, payment (Shop Pay, Apple Pay, cards), fraud detection and order creation. It’s the solution we recommend in most cases: Shopify checkout is one of the best-performing on the market, and you don’t have to handle any banking data. It can be customised in your brand colours.
Creating orders via the Admin API
In some cases, the order has to be created directly via the API, server-side, with Shopify’s Admin API:
- Trade orders: a reseller orders on their portal, and the order is created with their terms (dedicated prices, 30-day payment) via a draft order and then finalised. See our article on the Shopify B2B portal.
- Payment handled elsewhere: marketplace, signed quote, payment on invoice or through an external provider. The order is then recorded in Shopify to centralise stock, shipping and accounting.
- Quotes: a draft order is prepared by your team, then sent to the customer with a payment link.
Customer accounts, markets and languages
The Customer Account API lets you display the customer’s order history and addresses in your own interface. Shopify Markets remains usable: the Storefront API’s @inContext directive returns prices in the currency and language of the target country. For international SEO, hreflang tags need to be generated on the Next.js side, as explained in our article on multilingual websites.
SEO: an advantage, and a responsibility
A well-built headless store is extremely fast: static pages served from a CDN, optimised images, almost no unnecessary JavaScript, no apps injecting their scripts everywhere. That’s a major asset for Core Web Vitals and for conversion. But everything the Shopify theme did automatically becomes your responsibility:
- rendering product pages server-side or statically (see SSR, SSG or CSR);
- the sitemap, canonical tags and title and description tags;
- Product structured data, with price and availability;
- redirects, especially if you’re migrating from an existing Shopify theme;
- handling out-of-stock or deleted products.
Advantages and limits
The advantages
- Complete freedom of design and experience, without the theme’s constraints.
- First-class performance, even with a large catalogue.
- A single website for content and the store, with a single identity.
- The ability to connect other sources: CMS, configurator, PIM, ERP, CRM.
The limits
- A development cost significantly higher than a theme.
- Some Shopify apps don’t work without a theme (review widgets, upsells, loyalty programmes): you have to use their API or replace them.
- Technical maintenance to plan for, as with any custom website. See our article on website maintenance.
- Your team keeps the Shopify back office, but can no longer change the layout with the theme editor: the CMS takes over.
Next.js or Hydrogen?
Shopify offers its own headless framework, Hydrogen, with built-in hosting, Oxygen. It’s an excellent option if the store is the heart of the project. Next.js is more relevant when the website mixes a lot of content, other data sources or business features, and when your team or agency already works with it. Either way, the Storefront API is the same.
Who is it for?
Headless isn’t for a store that’s just starting out: a good, well-optimised Shopify theme remains more cost-effective, as we explain in our guide to creating a Shopify store. It becomes worthwhile for established brands that want a distinctive experience, stores where performance is a major issue, and projects that combine e-commerce, content and business tools.
That’s exactly the kind of project we love. Our Shopify agency in Marseille builds headless stores in Next.js, and our API integrations connect Shopify to your other tools. Let’s talk about your project: we’ll tell you frankly whether headless is the right choice for you.