commercetools Frontend Integration: Architecture and Practice
commercetools has no built-in UI — only API. This means the frontend must be built from scratch, and the stack choice directly affects performance and maintenance costs. The most mature ecosystem has formed around Next.js with @commercetools/platform-sdk. Over 5 years, we've implemented more than 20 headless projects — from e-commerce stores to B2B portals — and learned the hard lessons you won't have to repeat. Typical pain points: ConcurrentModification error when working with the cart, lost prices due to incorrect priceCurrency, and slow search without caching. To avoid these pitfalls, let's go over key architectural decisions and show how ISR gives TTFB 5x faster than traditional SSR, and syncing via Subscriptions updates search in seconds instead of hours.
Why Headless commercetools Is a Challenge for the Frontend
Unlike monolithic CMSs, commercetools provides neither rendering nor caching. All presentation logic falls on the frontend. This gives freedom but requires sound architecture: separating clients (server and client), incremental static generation, and version conflict handling. An unprepared team often ends up with N+1 queries, high TTFB, and lost cart data.
SDK and Client Initialization
npm install @commercetools/platform-sdk @commercetools/sdk-client-v2 \
@commercetools/sdk-middleware-auth @commercetools/sdk-middleware-http \
@commercetools/sdk-middleware-queue
Three clients for three contexts:
// lib/ctpClient.ts
import { createClient } from "@commercetools/sdk-client-v2";
import { createApiBuilderFromCtpClient } from "@commercetools/platform-sdk";
function buildClient(authMiddleware: Middleware) {
return createApiBuilderFromCtpClient(
createClient({
middlewares: [
authMiddleware,
createQueueMiddleware({ concurrency: 5 }),
createHttpMiddleware({
host: `https://api.${process.env.CTP_REGION}.commercetools.com`,
}),
],
})
).withProjectKey({ projectKey: process.env.CTP_PROJECT_KEY! });
}
export const serverApiRoot = buildClient(
createAuthMiddlewareForClientCredentialsFlow({
host: `https://auth.${process.env.CTP_REGION}.commercetools.com`,
projectKey: process.env.CTP_PROJECT_KEY!,
credentials: {
clientId: process.env.CTP_SERVER_CLIENT_ID!,
clientSecret: process.env.CTP_SERVER_CLIENT_SECRET!,
},
scopes: [`view_products:${process.env.CTP_PROJECT_KEY}`],
})
);
The server-side client is used in getStaticProps / RSC. The client-side client (with user token) is used only in the browser.
ISR Solves the Dynamic Pricing Problem
If you publish the catalog completely statically, prices become outdated. We use Incremental Static Regeneration with revalidate: 300 for product pages. This gives a TTFB of 80 ms and freshness of prices no older than 5 minutes. For catalogs with frequent updates — Redis cache on top of SDK with invalidation via webhook.
// app/catalog/[slug]/page.tsx (App Router)
import { serverApiRoot } from "@/lib/ctpClient";
export async function generateStaticParams() {
const products = await serverApiRoot
.productProjections()
.get({
queryArgs: {
limit: 500,
staged: false,
where: 'masterData(published = true)',
},
})
.execute();
return products.body.results.map((p) => ({ slug: p.slug["ru"] }));
}
export default async function ProductPage({
params,
}: {
params: { slug: string };
}) {
const result = await serverApiRoot
.productProjections()
.get({
queryArgs: {
where: `slug(ru = "${params.slug}")`,
expand: ["productType", "categories[*]"],
priceCurrency: "RUB",
priceChannel: "channel-key=storefront-ru",
},
})
.execute();
const product = result.body.results[0];
if (!product) notFound();
return <ProductDetail product={product} />;
}
Cart: Client State + API
The cart is stored in commercetools — cartId is saved in a cookie. No duplication in localStorage.
// hooks/useCart.ts
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { browserApiRoot } from "@/lib/ctpClientBrowser";
import Cookies from "js-cookie";
export function useCart() {
const queryClient = useQueryClient();
const cartId = Cookies.get("cart_id");
const { data: cart } = useQuery({
queryKey: ["cart", cartId],
queryFn: async () => {
if (!cartId) return null;
return (await browserApiRoot.carts().withId({ ID: cartId }).get().execute()).body;
},
enabled: !!cartId,
});
const addToCart = useMutation({
mutationFn: async ({
productId,
variantId,
quantity,
}: {
productId: string;
variantId: number;
quantity: number;
}) => {
if (!cartId) {
const newCart = await browserApiRoot.carts().post({
body: {
currency: "RUB",
store: { typeId: "store", key: "web-ru" },
lineItems: [{ productId, variantId, quantity }],
},
}).execute();
Cookies.set("cart_id", newCart.body.id, { expires: 30 });
return newCart.body;
}
return (await browserApiRoot.carts().withId({ ID: cartId }).post({
body: {
version: cart!.version,
actions: [{ action: "addLineItem", productId, variantId, quantity }],
},
}).execute()).body;
},
onSuccess: (updatedCart) => {
queryClient.setQueryData(["cart", updatedCart.id], updatedCart);
},
});
return { cart, addToCart };
}
Search with Algolia Sync
Commercetools does not provide full-text search with Algolia-level relevance. A productive solution is synchronization via Subscriptions:
// subscriptions/algolia-sync.ts
// Commercetools Subscription → SQS → Lambda → Algolia
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
const message = JSON.parse(record.body);
const { notificationType, resourceTypeId, resourceUserProvidedIdentifiers } = message;
if (resourceTypeId !== "product") continue;
const product = await serverApiRoot
.products()
.withId({ ID: message.resource.id })
.get({ queryArgs: { expand: ["productType"] } })
.execute();
if (notificationType === "ResourceDeleted") {
await algoliaIndex.deleteObject(message.resource.id);
} else {
await algoliaIndex.saveObject(transformForAlgolia(product.body));
}
}
}
Customer Authentication and Cart Merge
For login we use the Customer SDK. After successful authentication we merge the anonymous cart with the user's cart — this is standard for commercetools. Details of implementation in Next.js Route Handlers with httpOnly cookies.
Server-Side vs Client-Side Client
| Characteristic | Server-side client | Client-side client |
|---|---|---|
| Auth type | Client Credentials (service-to-service) | Password Flow (user token) |
| Scope | Full catalog, prices, inventory | Only current customer data |
| Caching | ISR/SSG with revalidate | None (only React Query client) |
| Security | env variables, never in browser | Token in httpOnly cookie |
| Performance | Very low TTFB (80-150 ms) | Depends on network (200-400 ms) |
How to Avoid Integration Mistakes: Common Issues and Our Process
-
409 ConcurrentModification — outdated
version. Solution: retry with fresh object. We add automatic retry. -
400 InvalidInput on cart — triggered Extension rejected the operation, read
extensionExtraInfo. -
Prices not showing — missing
priceCurrencyandpriceChannel. -
Slug not found — product not published (
staged: trueinstead offalse).
Stages and Timelines
| Stage | What we do | Duration |
|---|---|---|
| Analysis | Audit current architecture, integration scope, set up commercetools environments | 2-3 days |
| Design | Data schema, product types, taxonomy, SDK endpoints | 3-5 days |
| Implementation | Client setup, ISR catalog, cart, authentication, search | 10-15 days |
| Testing | Integration tests, load testing (N+1, cache) | 3-4 days |
| Deployment | CI/CD, monitoring, cache invalidation, documentation | 2-3 days |
What's Included
- Documentation: endpoint schema, type descriptions, request examples.
- Credentials: client_id/secret for server client, password flow for customers.
- Training: demonstration of SDK usage, explanation of common errors.
- Support: 2 weeks after deployment — bug fixes and consultations.
Why Choose Us
- 5+ years of experience with commercetools and headless platforms.
- 20+ successful projects for e-commerce and B2B.
- Guarantee: Code Review before every deployment, test coverage >80%.
- Certification: our engineers have completed official commercetools training.
Reducing infrastructure costs by 30-50% thanks to ISR and accelerating time-to-market with ready-made solutions — real results we back with metrics. Get a consultation for your project. We'll assess the scope and offer the optimal solution.







