Headless Commerce Architecture: Decoupling Frontend from Your Commerce Engine
An analysis of headless commerce architecture, comparison with monolithic platforms, and a practical guide to building a Next.js storefront connected to Magento or Shopify GraphQL APIs.
Headless commerce completely separates the frontend (presentation layer) from the backend commerce engine. Instead of using Magento or Shopify's built-in themes, you build a custom frontend with Next.js and communicate with the backend via API. The result is superior user experience and faster development velocity.
1. Monolithic vs Headless — When to Choose Which?
Monolithic is suitable when:
- Small team, tight timeline
- Limited customisation requirements
- Constrained budget
Headless is suitable when:
- Maximum page speed is required (Core Web Vitals targets)
- Multiple frontends needed: web, mobile app, kiosk, smart TV
- Team has strong React/Next.js engineers
- Frontend A/B testing independent of backend changes is needed
2. Architecture Overview
┌─────────────────────────────────────────────┐
│ Frontend Layer │
│ Next.js App (SSG/ISR/SSR hybrid) │
│ - Product pages (SSG + ISR) │
│ - Search (SSR) │
│ - Cart & Checkout (CSR) │
└──────────────┬──────────────────────────────┘
│ GraphQL / REST
┌──────────────▼──────────────────────────────┐
│ API Gateway (optional) │
│ - Rate limiting, auth, caching │
└──────────────┬──────────────────────────────┘
│
┌──────────────▼──────────────────────────────┐
│ Commerce Backend │
│ Magento 2 GraphQL / Shopify Storefront API │
│ - Catalog, Inventory, Pricing │
│ - Orders, Cart, Customer │
└─────────────────────────────────────────────┘
3. Connecting to Magento 2 GraphQL from Next.js
// lib/magento-client.ts
import { GraphQLClient } from 'graphql-request';
export const magentoClient = new GraphQLClient(
process.env.MAGENTO_GRAPHQL_URL!,
{
headers: {
'Content-Type': 'application/json',
'Store': process.env.MAGENTO_STORE_CODE ?? 'default',
},
}
);
const GET_PRODUCT = `
query GetProduct($urlKey: String!) {
products(filter: { url_key: { eq: $urlKey } }) {
items {
id
sku
name
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
discount { percent_off amount_off }
}
}
description { html }
media_gallery { url label position }
... on ConfigurableProduct {
configurable_options {
attribute_code
label
values { uid label swatch_data { value } }
}
variants {
attributes { uid code label value_index }
product {
sku
stock_status
price_range { minimum_price { final_price { value } } }
}
}
}
}
}
}
`;
export async function getProduct(urlKey: string) {
const data = await magentoClient.request(GET_PRODUCT, { urlKey });
return data.products.items[0] ?? null;
}
4. Static Generation with ISR
// app/products/[slug]/page.tsx
import { getProduct, getAllProductSlugs } from '@/lib/magento-client';
export async function generateStaticParams() {
const slugs = await getAllProductSlugs();
return slugs.map(slug => ({ slug }));
}
export const revalidate = 3600; // ISR: regenerate every 1 hour
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await getProduct(params.slug);
if (!product) notFound();
return <ProductDetailView product={product} />;
}
5. Cart Management with Server Actions
// app/actions/cart.ts
'use server';
import { cookies } from 'next/headers';
import { magentoClient } from '@/lib/magento-client';
const CREATE_CART = `mutation { createGuestCart { cart { id } } }`;
const ADD_TO_CART = `
mutation AddToCart($cartId: String!, $sku: String!, $qty: Float!) {
addSimpleProductsToCart(input: {
cart_id: $cartId
cart_items: [{ data: { sku: $sku, quantity: $qty } }]
}) {
cart {
total_quantity
prices { grand_total { value currency } }
}
}
}
`;
export async function addToCart(sku: string, qty: number = 1) {
const cookieStore = await cookies();
let cartId = cookieStore.get('cart_id')?.value;
if (!cartId) {
const data = await magentoClient.request(CREATE_CART);
cartId = data.createGuestCart.cart.id;
cookieStore.set('cart_id', cartId, { httpOnly: true, secure: true });
}
return magentoClient.request(ADD_TO_CART, { cartId, sku, qty });
}
6. Performance: Achieving Strong Core Web Vitals
// Lazy-load heavy components
const ProductGallery = dynamic(() => import('./ProductGallery'), {
loading: () => <GallerySkeleton />,
});
// Optimise LCP image
<Image
src={product.mainImage}
alt={product.name}
width={800}
height={800}
priority={true}
sizes="(max-width: 768px) 100vw, 50vw"
quality={85}
/>
Conclusion
Headless commerce delivers a clear competitive advantage in speed and developer experience. However, it requires a team experienced with both React frontends and commerce backend APIs. Ventra Rocket has successfully delivered multiple headless commerce projects, helping clients achieve Lighthouse scores above 90 and measurable conversion rate improvements.