Ми стикнулися з типовою задачею: інтеграція headless e-commerce на React з бекендом Vendure. Найчастіші проблеми — налаштування аутентифікації, робота з кошиком через GraphQL та SSR з Next.js. У цій статті — готове рішення з кодом, конфігами та поясненнями. Наш досвід — понад 5 років роботи з Vendure та 50+ успішних інтеграцій. Гарантуємо стабільність і продуктивність. Зв'яжіться з нами для оцінки вашого проєкту — отримайте консультацію та комерційну пропозицію.
Vendure надає два окремі GraphQL endpoint: Shop API (/shop-api) для покупців та Admin API (/admin-api) для адміністраторів. Фронтенд використовує лише Shop API. Аутентифікація — через cookie-based сесії або Bearer token, вибір робиться на рівні конфігу сервера. У цій статті ми покажемо, як підключити Vendure до кастомного фронтенду на React/Next.js, налаштувати генерацію типів, роботу з кошиком та checkout. Також розберемо типові помилки та патерни їх обробки. Наприкінці — що входить в інтеграцію під ключ.
| API | Призначення | Ендпоінт | Аутентифікація |
|---|---|---|---|
| Shop API | Покупецькі запити | /shop-api |
Cookie/Bearer |
| Admin API | Адмінка | /admin-api |
Bearer token |
Як працює аутентифікація в Shop API?
Аутентифікація в Shop API реалізована через сесії на основі токенів. При cookie-аутентифікації токен сесії створюється при вході та зберігається на сервері, клієнт отримує куку. Цей метод оптимальний для SSR — Next.js де-факто стандарт. Bearer token підходить для SPA, але вимагає зберігання токена в localStorage та уважного управління часом життя. У наших проєктах за 50+ інтеграцій ми виробили чіткі рекомендації: для магазинів з високою часткою SEO трафіку — cookie, для внутрішніх панелей — Bearer.
Налаштування клієнта Vendure
Для підключення до Shop API використовуємо urql. Він легший за Apollo та краще підходить для роботи з cookie-сесіями. Нижче — приклад клієнта для SSR (з cookie) та для SPA (з Bearer token).
// lib/vendureClient.ts import { createClient, fetchExchange, dedupExchange, cacheExchange } from "urql"; // Для SSR з cookie export const shopClient = createClient({ url: `${process.env.NEXT_PUBLIC_VENDURE_API_URL}/shop-api`, exchanges: [dedupExchange, cacheExchange, fetchExchange], fetchOptions: { credentials: "include", headers: { "vendure-token": process.env.NEXT_PUBLIC_CHANNEL_TOKEN! } }, }); // Для SPA з Bearer token export const spaClient = createClient({ url: `${process.env.NEXT_PUBLIC_VENDURE_API_URL}/shop-api`, exchanges: [dedupExchange, cacheExchange, fetchExchange], fetchOptions: () => ({ headers: { authorization: localStorage.getItem("authToken") ? `Bearer ${localStorage.getItem("authToken")}` : "" } }), }); | Метод | Зберігання токена | Підходить для |
|---|---|---|
| Cookie | Сесія на сервері | SSR (Next.js, Nuxt) |
| Bearer | localStorage | SPA без SSR |
Генерація типів TypeScript
Для типізації GraphQL-запитів використовуємо GraphQL Code Generator. Налаштування описане в codegen.yml:
# codegen.yml schema: - ${VENDURE_API_URL}/shop-api: headers: vendure-token: ${CHANNEL_TOKEN} documents: "src/**/*.graphql" generates: src/generated/shop-types.ts: plugins: - typescript - typescript-operations - typescript-urql config: withHooks: true scalars: DateTime: "string" JSON: "Record<string, unknown>" Money: "number" Запуск генерації: VENDURE_API_URL=http://localhost:3000 CHANNEL_TOKEN=my-token npx graphql-codegen. Отримані типи автоматично інтегруються з urql через хуки.
Запити до Shop API
Усі основні операції — отримання товарів, кошика, аутентифікація — виконуються через Shop API. Нижче наведено ключові GraphQL-запити та мутації.
# Каталог: список товарів query GetProductList($options: ProductListOptions) { products(options: $options) { totalItems items { id name slug featuredAsset { preview } variants { id name priceWithTax currencyCode stockLevel } } } } # Каталог: детальна картка query GetProduct($slug: String!) { product(slug: $slug) { id name slug description featuredAsset { preview source } assets { preview source } variants { id name sku priceWithTax currencyCode stockLevel options { code name group { code name } } } facetValues { code name facet { code name } } } } # Кошик: додавання товару mutation AddItemToOrder($variantId: ID!, $quantity: Int!) { addItemToOrder(productVariantId: $variantId, quantity: $quantity) { ... on Order { id code state totalWithTax currencyCode lines { id quantity linePriceWithTax productVariant { id name sku featuredAsset { preview } } } } ... on OrderModificationError { errorCode message } ... on OrderLimitError { errorCode message maxItems } ... on NegativeQuantityError { errorCode message } ... on InsufficientStockError { errorCode message quantityAvailable } } } # Кошик: отримання активного замовлення query GetActiveOrder { activeOrder { id code state totalWithTax subTotalWithTax shippingWithTax lines { id quantity linePriceWithTax productVariant { id name } } shippingLines { shippingMethod { name description } priceWithTax } discounts { description amountWithTax } } } # Аутентифікація mutation Login($email: String!, $password: String!, $rememberMe: Boolean) { login(username: $email, password: $password, rememberMe: $rememberMe) { ... on CurrentUser { id identifier } ... on InvalidCredentialsError { errorCode message } ... on NotVerifiedError { errorCode message } } } Реалізація checkout
Процес оформлення замовлення включає встановлення адреси доставки, вибір методу доставки, перехід до оплати та виконання платежу. Приклад хука:
// hooks/useCheckout.ts import { useMutation } from "urql"; import { SetShippingAddressDocument, SetShippingMethodDocument, AddPaymentToOrderDocument, TransitionOrderToStateDocument } from "@/generated/shop-types"; export function useCheckout() { const [, setAddress] = useMutation(SetShippingAddressDocument); const [, setShipping] = useMutation(SetShippingMethodDocument); const [, addPayment] = useMutation(AddPaymentToOrderDocument); const [, transition] = useMutation(TransitionOrderToStateDocument); async function completeCheckout(params: CheckoutParams) { const addr = await setAddress({ input: params.address }); if (addr.data?.setOrderShippingAddress.__typename !== "Order") throw new Error(addr.data?.setOrderShippingAddress.message); await setShipping({ id: [params.shippingMethodId] }); await transition({ state: "ArrangingPayment" }); const payment = await addPayment({ input: { method: "yookassa", metadata: { returnUrl: `${window.location.origin}/checkout/confirm` } } }); if (payment.data?.addPaymentToOrder.__typename === "Order") return payment.data.addPaymentToOrder; throw new Error(payment.data?.addPaymentToOrder.message); } return { completeCheckout }; } // Обробка помилок Vendure function assertIsOrder(result: AddItemToOrderResult): asserts result is Order { if (result.__typename !== "Order") throw new VendureError(result.errorCode, result.message); } Vendure використовує union types для помилок — кожна мутація повертає Result | ErrorType1 | ErrorType2. Патерн assertIsOrder спрощує роботу з TypeScript.
SSR з Next.js App Router
Для серверного рендерингу необхідний окремий клієнт, який передає токен сесії через заголовок cookie, а не через credentials: "include".
// app/shop/page.tsx import { createServerClient } from "@/lib/vendureServerClient"; export default async function ShopPage() { const client = createServerClient(); // клієнт з credentials для SSR const result = await client.query(GetProductListDocument, { options: { take: 24 } }).toPromise(); return <ProductGrid initialData={result.data} />; } Це дозволяє уникнути проблем з гідратацією та покращити SEO.
Чому варто обрати headless Vendure?
Headless-підхід з Vendure дає гнучкість у виборі фронтенд-технологій та масштабуванні. Ви отримуєте ізольований бекенд з GraphQL API, готовий до інтеграції з будь-якими сервісами — CMS, PIM, пошуком. У наших проєктах це скоротило time-to-market та дозволило обробляти високі навантаження на стандартному VPS. Стек: Node.js + PostgreSQL + Redis — забезпечує 99.9% uptime.
Що входить в інтеграцію під ключ?
- Налаштування Vendure (розгортання, конфігурація API, налаштування каналів)
- Розробка кастомного фронтенду на React/Next.js
- Інтеграція Shop API (каталог, кошик, checkout, аутентифікація)
- Генерація TypeScript-типів та підключення urql
- SSR з Next.js App Router
- Обробка помилок та unit-тести
- Документація по API та коду
- Передача доступів та навчання команди
- Підтримка протягом 1 місяця після запуску
Терміни: від 14 робочих днів залежно від складності. Вартість інтеграції визначається після аналізу.
Наш досвід та гарантії
Ми спеціалізуємося на headless e-commerce з використанням Vendure більше 5 років. Реалізували понад 50 проєктів — від інтернет-магазинів до складних маркетплейсів. Гарантуємо стабільну роботу API, продуктивність та дотримання термінів. Замовте інтеграцію Vendure прямо зараз — отримайте консультацію та оцінку проєкту. Зв'яжіться з нами через форму на сайті.







