Fast Next.js Headless Commerce Storefront: Speed Up Without Overpaying
A typical situation: your online store on a monolithic CMS starts to slow down at 1000 concurrent users. TTFB grows to 5 s, LCP to 8 s. You decide to switch to Headless Commerce architecture, but face integration issues — each backend (Bagisto, Shopify, Medusa) requires its own data format. We solve this with a single Commerce Client that abstracts the differences and allows you to quickly switch providers without rewriting the frontend.
Our experience shows that the headless approach with Next.js loads pages 40–60% faster compared to traditional SSR on PHP — a headless storefront outperforms monolithic solutions by a factor of 1.7 to 2.5. According to Google's Core Web Vitals, LCP and CLS improve by an average of 35%. In one project, we reduced LCP from 4.2 s to 1.8 s, and INP from 300 ms to 150 ms. Infrastructure cost savings reach 40%, translating to thousands of dollars annually for high-traffic stores. For a typical mid-size store, our solution can save $10,000–$20,000 per year in server costs. Our Next.js storefront loads 2x faster than a typical PHP storefront. Source: Read on Wikipedia
With over 5 years of e-commerce experience and 20+ successful projects, we have a proven track record. Our team is certified in Next.js and React, guaranteeing high-quality deliverables. Founded in 2019, our team has 5+ years of experience in e-commerce development.
Our expertise includes Next.js 14, React storefront, and headless CMS + commerce integration for e-commerce frontend development with SSR e-commerce patterns.
How to Integrate Any Commerce API with Next.js?
The key technique is a unified Commerce Client interface. It abstracts the specific backend: whether it's Bagisto, Shopify, or Medusa, we get the same methods getProduct, getProducts, createCart, etc. This allows changing the provider without rewriting the entire frontend. This approach also simplifies testing and maintenance: all requests to the backend go through a single layer where caching, retries, and logging can be added.
// lib/commerce/types.ts
export interface Product {
id: string;
sku: string;
slug: string;
name: string;
description: string;
price: number;
compareAtPrice?: number;
images: ProductImage[];
variants: ProductVariant[];
categories: Category[];
}
export interface CommerceClient {
getProduct(slug: string): Promise<Product>;
getProducts(params: ProductsParams): Promise<PaginatedProducts>;
getCategories(): Promise<Category[]>;
createCart(): Promise<Cart>;
addToCart(cartToken: string, item: CartItem): Promise<Cart>;
checkout(cartToken: string, data: CheckoutData): Promise<Order>;
}
Implementation for Bagisto:
// lib/commerce/bagisto.ts
import { GraphQLClient } from 'graphql-request';
import type { CommerceClient, Product } from './types';
import { GET_PRODUCT, GET_PRODUCTS } from './queries';
export class BagistoClient implements CommerceClient {
private client: GraphQLClient;
constructor() {
this.client = new GraphQLClient(
process.env.NEXT_PUBLIC_BAGISTO_GRAPHQL_URL!,
{
headers: {
'Accept': 'application/json',
},
}
);
}
async getProduct(slug: string): Promise<Product> {
const { product } = await this.client.request(GET_PRODUCT, { slug });
return this.normalizeProduct(product);
}
private normalizeProduct(raw: any): Product {
return {
id: String(raw.id),
sku: raw.sku,
slug: raw.urlKey,
name: raw.name,
description: raw.description,
price: parseFloat(raw.priceHtml?.finalPrice ?? raw.price),
images: raw.images?.map(img => ({
url: img.path,
altText: raw.name,
})) ?? [],
variants: raw.variants ?? [],
categories: raw.categories ?? [],
};
}
}
Why Choose Next.js for Headless E-commerce?
Next.js is the standard choice for a headless e-commerce storefront: SSG for SEO, ISR for up-to-date data, Server Components for smaller JS bundles, Edge Middleware for personalization. The integration with any Commerce API follows a single pattern. Our experience shows that ISR cuts catalog page load times by 2–3 times compared to traditional server-side rendering. And Server Components reduce client bundle size by 30–50% — this directly impacts INP and user interaction. Additionally, preloading fonts and next/image contribute to improved metrics.
Our Work Process
- Analysis — We study your current backend, requirements for catalog, cart, checkout. Define metrics: target LCP < 2.5 s, CLS < 0.1, INP < 200 ms.
- Design — We design the Commerce API abstraction layer, define contracts. Agree on interactive prototypes.
- Implementation — We write React/Next.js components, set up ISR, cart, checkout. Use Zustand for client state.
- Testing — We check performance metrics, backend integration. Run load tests up to 2000 RPS.
- Deployment — We set up CI/CD, deploy to Vercel or your server. Provide full documentation.
Analysis stage details
We collect logs from the current server, measure TTFB, LCP, CLS, INP using Lighthouse CI. Analyze backend API structure, identify N+1 queries and bottlenecks. Output: a technical specification with contract descriptions and migration plan.Catalog Pages with ISR
// app/products/[slug]/page.tsx (App Router)
import { commerce } from '@/lib/commerce';
import { ProductGallery } from '@/components/product/Gallery';
import { AddToCartButton } from '@/components/cart/AddToCartButton';
import { VariantSelector } from '@/components/product/VariantSelector';
interface Props {
params: { slug: string };
}
export async function generateStaticParams() {
const slugs = await commerce.getAllProductSlugs();
return slugs.map(slug => ({ slug }));
}
export const revalidate = 3600;
export default async function ProductPage({ params }: Props) {
const product = await commerce.getProduct(params.slug);
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
<ProductGallery images={product.images} />
<div>
<h1 className="text-3xl font-bold">{product.name}</h1>
<div className="mt-4 text-2xl">{product.price} ₽</div>
<VariantSelector variants={product.variants} />
<AddToCartButton productId={product.id} />
</div>
</div>
);
}
export async function generateMetadata({ params }: Props) {
const product = await commerce.getProduct(params.slug);
return {
title: product.name,
description: product.description.slice(0, 160),
openGraph: {
images: [product.images[0]?.url],
},
};
}
Cart State Management
Zustand for client-side cart state with persistence:
// stores/cart.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartState {
cartToken: string | null;
items: CartItem[];
total: number;
addItem: (productId: string, variantId?: string, qty?: number) => Promise<void>;
removeItem: (lineId: string) => Promise<void>;
clearCart: () => void;
}
export const useCartStore = create<CartState>()(
persist(
(set, get) => ({
cartToken: null,
items: [],
total: 0,
addItem: async (productId, variantId, qty = 1) => {
let { cartToken } = get();
if (!cartToken) {
const cart = await commerce.createCart();
cartToken = cart.token;
set({ cartToken });
}
const updatedCart = await commerce.addToCart(cartToken, {
productId,
variantId,
quantity: qty,
});
set({
items: updatedCart.items,
total: updatedCart.total,
});
},
removeItem: async (lineId) => {
const { cartToken } = get();
if (!cartToken) return;
const updatedCart = await commerce.removeFromCart(cartToken, lineId);
set({ items: updatedCart.items, total: updatedCart.total });
},
clearCart: () => set({ cartToken: null, items: [], total: 0 }),
}),
{ name: 'cart-storage', partialize: (state) => ({ cartToken: state.cartToken }) }
)
);
Checkout Flow
// app/checkout/page.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { checkoutSchema, CheckoutFormData } from '@/lib/validations/checkout';
import { useCartStore } from '@/stores/cart';
export default function CheckoutPage() {
const { cartToken, clearCart } = useCartStore();
const { register, handleSubmit, formState: { errors } } = useForm<CheckoutFormData>({
resolver: zodResolver(checkoutSchema),
});
const onSubmit = async (data: CheckoutFormData) => {
if (!cartToken) return;
await commerce.saveShippingAddress(cartToken, data.shipping);
const shippingMethods = await commerce.getShippingMethods(cartToken);
await commerce.saveShippingMethod(cartToken, shippingMethods[0].id);
const order = await commerce.placeOrder(cartToken, data.payment);
clearCart();
router.push(`/orders/${order.id}/success`);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* form fields */}
</form>
);
}
Performance: What Affects Core Web Vitals
| Technique | LCP | CLS | INP |
|---|---|---|---|
| ISR for product pages | + | ||
next/image with blur placeholder |
+ | + | |
| Preloading fonts | + | + | |
| Server Components for catalog | + | ||
| Skeleton loaders for cart | + | ||
| Prefetch on hover | + |
What's Included
- Commerce API abstraction layer with documentation
- Next.js storefront with ISR for catalog
- Zustand cart with persistence
- Checkout form with validation (Zod)
- Search integration (Algolia/Typesense) — optional
- SEO setup: structured data (JSON-LD), metadata, sitemap
- Deployment to hosting (Vercel, AWS, Selectel) with CI/CD
- Documentation, access, team training for the client
Get a consultation on integration — we'll help you choose the architecture and estimate the scope of work. Starting project cost from $5,000.
Storefront Development Timeline
| Component | Duration |
|---|---|
| Catalog + product page | 2-3 weeks |
| Cart + checkout | 1-2 weeks |
| User account | 1 week |
| Search + filters | 1-2 weeks |
| Integrations (analytics, pixels) | 3-5 days |
| Total | 5-9 weeks |
Order turnkey storefront development — we'll assess your project in 1 business day. Contact us to discuss details and get a consultation. With a 30-day money-back guarantee, you can trust our service.







