Back to Blog
ecommerce9 min read

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.

V
By Ventra Rocket
·Published on 25 February 2026
#Headless Commerce#Next.js#GraphQL#Magento#Architecture

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.

Headless Commerce Architecture: Decoupling Frontend from Your Commerce Engine | Ventra Rocket