מצב טיפוסי: השקתם 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 — קבלו חנות עובדת מאפס תוך שבועיים. קבלו ייעוץ לפרויקט שלכם — צרו קשר להערכה.







