Мы решали задачу интеграции headless e-commerce на React с бэкендом Vendure. Типичные проблемы: настройка аутентификации, работа с корзиной через GraphQL, SSR с Next.js. В этой статье — готовое решение с кодом, конфигами и пояснениями. Наш опыт — более 5 лет работы с Vendure и 50+ успешных интеграций. Средняя экономия бюджета при переходе на headless — до 40%, сроки вывода на рынок сокращаются до 2 недель. Гарантируем стабильность и производительность. Свяжитесь с нами для оценки вашего проекта — получите консультацию и коммерческое предложение.
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 на 40% и позволило обрабатывать до 10 000 запросов в секунду на стандартном 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 рабочих дней в зависимости от сложности. Стоимость интеграции под ключ — от $2 000 за типовой проект. Экономия на эксплуатации — до $5 000 в год за счёт оптимизации.
Наш опыт и гарантии
Мы специализируемся на headless e-commerce с использованием Vendure более 5 лет. Реализовали более 50 проектов — от интернет-магазинов до сложных маркетплейсов. Гарантируем стабильную работу API, производительность и соблюдение сроков. Закажите интеграцию Vendure прямо сейчас — получите консультацию и оценку проекта. Свяжитесь с нами через форму на сайте.







