commercetools Frontend Integration: Architecture and Practice

commercetools Frontend Integration: Architecture and Practice

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1284
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1240
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1031
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    553

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 priceCurrency and priceChannel.
  • Slug not found — product not published (staged: true instead of false).

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.