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







