Ми стикнулися з типовою задачею: інтеграція 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 прямо зараз — отримайте консультацію та оцінку проєкту. Зв'яжіться з нами через форму на сайті.







