מצב טיפוסי: השקתם backend של Medusa, אבל ה-frontend של Next.js זורק שגיאות hydration mismatch, העגלה לא נשמרת אחרי רענון, ותגי SEO לא נוצרים. 80% מבעיות הביצועים נובעות משאילתות N+1 ל-Store API. זמן טעינת העמוד הממוצע יורד ב-40% לאחר הגדרת batch requests נכונה (ראו Store API). לצוות שלנו יש 5 שנות ניסיון במסחר אלקטרוני headless, עם למעלה מ-50 פרויקטים מוצלחים, מתוכם 15+ על Medusa. צרו קשר לייעוץ חינם — נעזור לכם לבחור את הסטACK האופטימלי.
בעיות עיקריות באינטגרציית Medusa
שאילתות N+1 ל-Store API בעת בניית קטלוג — כל מוצר מביא variants ומחירים בנפרד, מה שהורג את LCP. פתרון: אנו מיישמים batch requests באמצעות medusaClient.store.product.list עם שדות *variants,*variants.prices. זה מפחית קריאות HTTP מ-100 ל-5 בעת טעינת קטלוג של 50 מוצרים.
אובדן הקשר עגלה במהלך SSR — הלקוח רואה עגלה ריקה בטעינה הראשונה. אנו מתקנים זאת על ידי שמירת cart_id ב-localStorage ושחזורו באמצעות Cart Context.
תשלום איטי — עקב קריאות API סינכרוניות. אנו מייעלים על ידי parallelizing של בקשות כתובת ושיטת משלוח.
למה Next.js היא הבחירה האופטימלית עבור Medusa
Next.js עם App Router מציעה איזון אידיאלי בין דינמיות ו-SEO: דפי מוצר נוצרים כסטטיים (generateStaticParams), בעוד העגלה והתשלום עובדים דרך 'use client'. בניגוד ל-Gatsby, שבו העגלה דורשת אי SPA נפרד, Next.js מאפשרת מעבר גמיש בין SSR, SSG ו-ISR. זה מפחית LCP ב-30% ומפשט את התחזוקה. התיעוד של Medusa מאשר ש-Next.js היא ה-framework המומלץ למסחר אלקטרוני headless.
הגדרת Medusa JS SDK ב-Next.js
התקינו את החבילה והטיפוסים:
npm install @medusajs/js-sdk @medusajs/types צרו client עם base URL, סוג אימות ו-publishableKey:
// lib/medusa/client.ts import Medusa from '@medusajs/js-sdk'; export const medusaClient = new Medusa({ baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!, auth: { type: 'session', // или 'jwt' для headless }, publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY, }); השתמשו ב-client ב-Server Components עבור SSR או ב-client components עבור דינמיות. טעות טיפוסית: אי מתן publishableKey — ה-SDK מחזיר 401.
שחזור העגלה בטעינה הראשונה
צרו context שמשחזר את העגלה מ-npm install @medusajs/js-sdk @medusajs/types בעת האתחול ומספק שיטות // lib/medusa/client.ts import Medusa from '@medusajs/js-sdk'; export const medusaClient = new Medusa({ baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!, auth: { type: 'session', // или 'jwt' для headless }, publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY, }); , localStorage, addItem:
// context/cart-context.tsx 'use client'; import { createContext, useContext, useEffect, useState } from 'react'; import { medusaClient } from '@/lib/medusa/client'; import type { HttpTypes } from '@medusajs/types'; type CartContextType = { cart: HttpTypes.StoreCart | null; addItem: (variantId: string, quantity: number) => Promise<void>; removeItem: (lineItemId: string) => Promise<void>; updateItem: (lineItemId: string, quantity: number) => Promise<void>; isLoading: boolean; }; const CartContext = createContext<CartContextType | null>(null); export function CartProvider({ children }: { children: React.ReactNode }) { const [cart, setCart] = useState<HttpTypes.StoreCart | null>(null); const [isLoading, setIsLoading] = useState(false); useEffect(() => { const cartId = localStorage.getItem('cart_id'); if (cartId) { medusaClient.store.cart.retrieve(cartId) .then(({ cart }) => setCart(cart)) .catch(() => localStorage.removeItem('cart_id')); } }, []); const addItem = async (variantId: string, quantity: number) => { setIsLoading(true); try { let currentCart = cart; // Создаём корзину, если не существует if (!currentCart) { const { cart: newCart } = await medusaClient.store.cart.create({ region_id: process.env.NEXT_PUBLIC_MEDUSA_REGION_ID, }); localStorage.setItem('cart_id', newCart.id); currentCart = newCart; } const { cart: updatedCart } = await medusaClient.store.cart.createLineItem( currentCart.id, { variant_id: variantId, quantity } ); setCart(updatedCart); } finally { setIsLoading(false); } }; const removeItem = async (lineItemId: string) => { if (!cart) return; setIsLoading(true); try { const { cart: updatedCart } = await medusaClient.store.cart.deleteLineItem( cart.id, lineItemId ); setCart(updatedCart); } finally { setIsLoading(false); } }; const updateItem = async (lineItemId: string, quantity: number) => { if (!cart) return; const { cart: updatedCart } = await medusaClient.store.cart.updateLineItem( cart.id, lineItemId, { quantity } ); setCart(updatedCart); }; return ( <CartContext.Provider value={{ cart, addItem, removeItem, updateItem, isLoading }}> {children} </CartContext.Provider> ); } export const useCart = () => { const ctx = useContext(CartContext); if (!ctx) throw new Error('useCart must be used within CartProvider'); return ctx; }; יישום Multi-Region ב-Next.js ו-Medusa
השתמשו ב-middleware כדי לקבוע את האזור לפי geolocation ולהעביר removeItem עם כל בקשה:
// middleware.ts import { NextRequest, NextResponse } from 'next/server'; const REGION_MAP: Record<string, string> = { RU: process.env.MEDUSA_REGION_RU!, BY: process.env.MEDUSA_REGION_BY!, DE: process.env.MEDUSA_REGION_EU!, DEFAULT: process.env.MEDUSA_REGION_DEFAULT!, }; export function middleware(request: NextRequest) { const country = request.geo?.country ?? 'DEFAULT'; const regionId = REGION_MAP[country] ?? REGION_MAP.DEFAULT; const response = NextResponse.next(); response.cookies.set('medusa_region', regionId, { maxAge: 60 * 60 * 24, sameSite: 'lax', }); return response; } מה לבחור: Gatsby או Next.js?
אם המבחר משתנה לעיתים רחוקות — Gatsby עם הפלאגין gatsby-source-medusa יוצר דפים סטטיים שנטענים מיידית. העגלה מיושמת דרך שכבת client נפרדת, והתשלום מועבר לשרת Medusa. זה זול ומהיר יותר עבור חנויות פשוטות.
// gatsby-config.ts import type { GatsbyConfig } from 'gatsby'; const config: GatsbyConfig = { plugins: [ { resolve: 'gatsby-source-medusa', options: { storeUrl: process.env.GATSBY_MEDUSA_BACKEND_URL, publishableApiKey: process.env.GATSBY_MEDUSA_PUBLISHABLE_KEY, entities: ['products', 'collections', 'regions'], batchSize: 100, }, }, ], }; export default config; | פרמטר | Next.js | Gatsby |
|---|---|---|
| דינמיות עגלה | React Server + Client | אי SPA נפרד |
| SEO | SSG/ISR/SSR לכל עמוד | SSG מלא |
| זמן בנייה | מיידי (ISR) | ארוך עם >1000 מוצרים |
| גמישות | גבוהה | בינונית |
Next.js מנצחת בביצועים ובפרודוקטיביות הצוות. Gatsby הוא הטוב ביותר כאשר יצירה סטטית היא קריטית.
המלצות נוספות לבחירה
אם יש לכם יותר מ-10,000 מוצרים, שקלו Next.js עם ISR כדי להימנע מבנייה מחדש של כל הקטלוג בכל שינוי. Gatsby מתאים לקטלוגים עד 1,000 מוצרים עם עדכונים נדירים.לוחות זמנים לאינטגרציה
| סוג משימה | לוח זמנים משוער |
|---|---|
| Next.js Storefront Starter + חיבור + התאמה אישית | 2–3 שבועות |
| Frontend מותאם אישית של Next.js מאפס (App Router, SSR/SSG, עגלה, תשלום) | 6–10 שבועות |
| קטלוג SSG של Gatsby עם עגלה דינמית (היברידי) | 4–6 שבועות |
| חנות Multi-Region עם לוקליזציה של URL ותוכן | +2–3 שבועות להערכה הבסיסית |
העלות מחושבת באופן אישי ונקבעת בשלב החוזה.
מה כלול בעבודה
- תיעוד ארכיטקטוני (סכמת נתונים, זרימת בקשות)
- כתיבת קוד ו-code review
- הגדרת CI/CD ופריסה ל-Vercel/אחסון
- הדרכת הצוות שלכם בשימוש ב-Medusa SDK
- תמיכה מובטחת לחודש לאחר ההשקה
תהליך העבודה
- ניתוח — אנו לומדים את מאפייני החנות ודרישות ה-frontend.
- עיצוב — אנו בוחרים את הסטACK (Next.js/Gatsby), מתכננים ארכיטקטורת נתונים ורכיבים.
- יישום — אנו כותבים קוד, מגדירים SDK, עגלה, תשלום, multi-region.
- בדיקות — אנו מוודאים עמידה ב-Core Web Vitals ותרחישי קונה.
- פריסה — אנו מגדירים CI/CD, פורסים ל-Vercel או לאחסון שלכם.
הזמינו אינטגרציית Medusa — קבלו חנות עובדת מאפס תוך שבועיים. קבלו ייעוץ לפרויקט שלכם — צרו קשר להערכה חינם.







