שילוב פרונטאנד עם commercetools: ארכיטקטורה ופרקטיקה

מכיוון ש-commercetools אינו כולל ממשק מוכן, יש לבנות את הפרונטאנד מאפס, וטעויות ארכיטקטוניות עלולות לעלות ביוקר. אנו מפתחים אינטגרציית headless עם Next.js ו-React, ומגדירים את ה-API וה-SDK של commercetools במתכונת turnkey. הצוות שלנו מטפל בכל המחזור—מעיצוב ועד תמיכה—כך שאתם מקבלים פתרון אמין וסקלבילי ללא חריגות מלוחות זמנים.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
שילוב פרונטאנד עם commercetools: ארכיטקטורה ופרקטיקה
בינוני
~5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

commercetools Frontend Integration: ארכיטקטורה ופרקטיקה

ל-commercetools אין ממשק משתמש מובנה — רק API. משמעות הדבר היא שהפרונטאנד חייב להיבנות מאפס, ובחירת הטכנולוגיה משפיעה ישירות על ביצועים ועלויות תחזוקה. המערכת האקולוגית הבוגרת ביותר נוצרה סביב Next.js עם @commercetools/platform-sdk. במשך יותר מ-5 שנים, יישמנו יותר מ-20 פרויקטים headless — מחנויות איקומרס ועד פורטלי B2B — ולמדנו את הלקחים הקשים שלא תצטרכו לחזור עליהם. נקודות כאב אופייניות: שגיאת ConcurrentModification בעבודה עם עגלת הקניות, מחירים שאבדו עקב priceCurrency שגוי, וחיפוש איטי ללא קאש. כדי להימנע ממלכודות אלה, נעבור על החלטות ארכיטקטוניות מרכזיות ונראה כיצד ISR נותן TTFB מהיר פי 5 מ-SSR מסורתי, וסנכרון דרך Subscriptions מעדכן חיפוש בשניות במקום שעות.

למה Headless commercetools הוא אתגר לפרונטאנד

בניגוד ל-CMS מונוליטיים, commercetools אינו מספק רינדור או קאש. כל לוגיקת התצוגה נופלת על הפרונטאנד. זה נותן חופש אך דורש ארכיטקטורה נכונה: הפרדת קליינטים (שרת וקליינט), יצירת דפים סטטיים אינקרמנטלית, וטיפול בקונפליקטי גרסאות. צוות לא מוכן מגיע לעיתים קרובות לשאילתות N+1, TTFB גבוה, ואובדן נתוני עגלה.

SDK ואתחול קליינט

npm install @commercetools/platform-sdk @commercetools/sdk-client-v2 \
 @commercetools/sdk-middleware-auth @commercetools/sdk-middleware-http \
 @commercetools/sdk-middleware-queue

שלושה קליינטים לשלושה הקשרים:

// 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}`],
  })
);

הקליינט בצד השרת משמש ב-npm install @commercetools/platform-sdk @commercetools/sdk-client-v2 \ @commercetools/sdk-middleware-auth @commercetools/sdk-middleware-http \ @commercetools/sdk-middleware-queue / RSC. הקליינט בצד הלקוח (עם טוקן משתמש) משמש רק בדפדפן.

ISR פותר את בעיית התמחור הדינמי

אם מפרסמים את הקטלוג באופן סטטי לחלוטין, המחירים מתיישנים. אנו משתמשים ב-Incremental Static Regeneration עם // 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}`], }) ); עבור דפי מוצר. זה נותן TTFB של 80 אלפיות השנייה וטריות מחירים שלא עולה על 5 דקות. עבור קטלוגים עם עדכונים תכופים — קאש Redis על גבי SDK עם invalidation דרך 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} />;
}

עגלת קניות: State בצד הלקוח + API

העגלה נשמרת ב-commercetools — getStaticProps נשמר בקוקי. ללא שכפול ב-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 };
}

חיפוש עם סנכרון Algolia

commercetools אינו מספק חיפוש טקסט מלא ברמת הרלוונטיות של Algolia. פתרון פרודוקטיבי הוא סנכרון דרך 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 SDK. לאחר אימות מוצלח אנו ממזגים את העגלה האנונימית עם עגלת המשתמש — זה סטנדרטי עבור commercetools. פרטי היישום ב-Next.js Route Handlers עם קוקיז httpOnly.

קליינט בצד השרת לעומת קליינט בצד הלקוח

מאפיין קליינט בצד השרת קליינט בצד הלקוח
סוג אימות Client Credentials (service-to-service) Password Flow (טוקן משתמש)
היקף קטלוג מלא, מחירים, מלאי רק נתוני לקוח נוכחיים
קאש ISR/SSG עם revalidate ללא (רק React Query client)
אבטחה משתני סביבה, לעולם לא בדפדפן טוקן בקוקי httpOnly
ביצועים TTFB נמוך מאוד (80-150 אלפיות השנייה) תלוי ברשת (200-400 אלפיות השנייה)

איך להימנע מטעויות אינטגרציה: בעיות נפוצות והתהליך שלנו

  • 409 ConcurrentModification — revalidate: 300 מיושן. פתרון: ניסיון חוזר עם אובייקט מעודכן. אנו מוסיפים ניסיון חוזר אוטומטי.
  • 400 InvalidInput על העגלה — Extension שהופעל דחה את הפעולה, קרא // 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} />; } .
  • מחירים לא מוצגים — חסר cartId ו-// 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 }; } .
  • Slug לא נמצא — המוצר לא פורסם (// 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)); } } } במקום version).

שלבים ולוחות זמנים

שלב מה אנחנו עושים משך
ניתוח ביקורת ארכיטקטורה נוכחית, היקף אינטגרציה, הגדרת סביבות commercetools 2-3 ימים
עיצוב סכמת נתונים, סוגי מוצרים, טקסונומיה, נקודות קצה של SDK 3-5 ימים
יישום הגדרת קליינט, קטלוג ISR, עגלה, אימות, חיפוש 10-15 ימים
בדיקות בדיקות אינטגרציה, בדיקות עומס (N+1, קאש) 3-4 ימים
פריסה CI/CD, ניטור, invalidation קאש, תיעוד 2-3 ימים

מה כלול

  • תיעוד: סכמת נקודות קצה, תיאורי סוגים, דוגמאות בקשות.
  • אישורים: client_id/secret לקליינט שרת, password flow ללקוחות.
  • הדרכה: הדגמת שימוש ב-SDK, הסבר על שגיאות נפוצות.
  • תמיכה: שבועיים לאחר הפריסה — תיקוני באגים וייעוץ.

למה לבחור בנו

  • 5+ שנות ניסיון עם commercetools ופלטפורמות headless.
  • 20+ פרויקטים מוצלחים לאיקומרס ו-B2B.
  • אחריות: Code Review לפני כל פריסה, כיסוי בדיקות >80%.
  • הסמכה: המהנדסים שלנו עברו הכשרה רשמית של commercetools.

הפחתת עלויות תשתית ב-30-50% בזכות ISR והאצת זמן היציאה לשוק עם פתרונות מוכנים — תוצאות אמיתיות שאנו מגבים במדדים. קבלו ייעוץ לפרויקט שלכם. נעריך את ההיקף ונציע את הפתרון האופטימלי.