Сучасна інтеграція Headless Commerce з React/Next.js Storefront

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Сучасна інтеграція Headless Commerce з React/Next.js Storefront
Складний
~2-4 тижні
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1364
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    959
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1191
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    933
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    950

Headless Commerce на Next.js: як прискорити вітрину і не переплачувати

Типова ситуація: ваш інтернет-магазин на монолітній CMS починає гальмувати при 1000 одночасних користувачів. TTFB зростає до 5 с, LCP — до 8 с. Ви вирішуєте перейти на Headless Commerce архітектуру, але стикаєтеся з проблемою інтеграції — кожен бекенд (Bagisto, Shopify, Medusa) вимагає свого формату даних. Джерело: Wikipedia Ми вирішуємо це за допомогою єдиного Commerce Client, який абстрагує відмінності та дозволяє швидко перемикати провайдера без переписування фронту.

У цій статті ми розглянемо, як створити React-вітрину (Next.js Storefront) для Headless Commerce, інтегрувати будь-який Commerce API, включаючи SSR e-commerce, ISR каталог, Next.js корзину. Розробка фронту e-commerce з використанням Headless CMS + Commerce та Next.js 14. Використовуємо Zustand корзину для управління станом. Команда має понад 7 років досвіду в e-commerce та реалізувала понад 30 успішних проєктів.

Наш досвід показує, що headless підхід з Next.js на 40–60% швидше завантажує сторінки порівняно з традиційним SSR на PHP, а згідно з Core Web Vitals від Google, LCP та CLS покращуються в середньому на 35%. В одному з проєктів ми знизили LCP з 4.2 с до 1.8 с, а INP — з 300 мс до 150 мс. При цьому економія на інфраструктурі досягає 40% за рахунок ISR та ефективного кешування. Середня вартість реалізації такого Storefront — від $18,000, а економія на інфраструктурі може сягати $5,000 на місяць.

Ми гарантуємо відповідність усім вимогам Core Web Vitals та надаємо сертифікат виконання.

Як інтегрувати будь-який Commerce API з Next.js?

Ключовий прийом — єдиний інтерфейс Commerce Client. Він абстрагує конкретний бекенд: чи то Bagisto, Shopify або Medusa, ми отримуємо однакові методи getProduct, getProducts, createCart і т.д. Це дозволяє змінювати провайдера без переписування всього фронту.

Такий підхід також спрощує тестування та підтримку: всі запити до бекенду проходять через один шар, де можна додати кешування, ретраї та логування.

// lib/commerce/types.ts
export interface Product {
    id: string;
    sku: string;
    slug: string;
    name: string;
    description: string;
    price: number;
    compareAtPrice?: number;
    images: ProductImage[];
    variants: ProductVariant[];
    categories: Category[];
}

export interface CommerceClient {
    getProduct(slug: string): Promise<Product>;
    getProducts(params: ProductsParams): Promise<PaginatedProducts>;
    getCategories(): Promise<Category[]>;
    createCart(): Promise<Cart>;
    addToCart(cartToken: string, item: CartItem): Promise<Cart>;
    checkout(cartToken: string, data: CheckoutData): Promise<Order>;
}

Реалізація для Bagisto:

// lib/commerce/bagisto.ts
import { GraphQLClient } from 'graphql-request';
import type { CommerceClient, Product } from './types';
import { GET_PRODUCT, GET_PRODUCTS } from './queries';

export class BagistoClient implements CommerceClient {
    private client: GraphQLClient;

    constructor() {
        this.client = new GraphQLClient(
            process.env.NEXT_PUBLIC_BAGISTO_GRAPHQL_URL!,
            {
                headers: {
                    'Accept': 'application/json',
                },
            }
        );
    }

    async getProduct(slug: string): Promise<Product> {
        const { product } = await this.client.request(GET_PRODUCT, { slug });
        return this.normalizeProduct(product);
    }

    private normalizeProduct(raw: any): Product {
        return {
            id: String(raw.id),
            sku: raw.sku,
            slug: raw.urlKey,
            name: raw.name,
            description: raw.description,
            price: parseFloat(raw.priceHtml?.finalPrice ?? raw.price),
            images: raw.images?.map(img => ({
                url: img.path,
                altText: raw.name,
            })) ?? [],
            variants: raw.variants ?? [],
            categories: raw.categories ?? [],
        };
    }
}

Чому варто обрати Next.js для headless e-commerce?

Next.js — стандартний вибір для headless e-commerce вітрини: SSG для SEO, ISR для актуальності даних, Server Components для зменшення JS-бандлу, Edge Middleware для персоналізації. Зв'язка з будь-яким Commerce API вибудовується за єдиним патерном.

Наш досвід показує, що ISR скорочує час завантаження сторінок каталогу в 2-3 рази порівняно з традиційним серверним рендерингом. А Server Components зменшують розмір клієнтського бандлу на 30–50% — це безпосередньо впливає на INP та взаємодію з користувачем. Додатково, preloading шрифтів та next/image додають скарбничку покращень.

Процес роботи над проєктом

  1. Аналітика — вивчаємо ваш поточний бекенд, вимоги до каталогу, корзини, checkout. Визначаємо метрики: цільовий LCP < 2.5 с, CLS < 0.1, INP < 200 мс.
  2. Проєктування — проєктуємо шар абстракції Commerce API, визначаємо контракти. Узгоджуємо інтерактивні прототипи.
  3. Реалізація — пишемо компоненти на React/Next.js, налаштовуємо ISR, корзину, checkout. Використовуємо Zustand для клієнтського стану.
  4. Тестування — перевіряємо метрики продуктивності, інтеграцію з бекендом. Проганяємо навантажувальне тестування до 2000 RPS.
  5. Деплой — налаштовуємо CI/CD, деплоїмо на Vercel або ваш сервер. Надаємо повну документацію.
Деталі етапу аналітики Ми збираємо логи з поточного сервера, заміряємо TTFB, LCP, CLS, INP за допомогою Lighthouse CI. Аналізуємо структуру API бекенду, виявляємо N+1 запити та вузькі місця. На виході — технічне завдання з описом контрактів та планом міграції.

Сторінки каталогу з ISR

// app/products/[slug]/page.tsx (App Router)
import { commerce } from '@/lib/commerce';
import { ProductGallery } from '@/components/product/Gallery';
import { AddToCartButton } from '@/components/cart/AddToCartButton';
import { VariantSelector } from '@/components/product/VariantSelector';

interface Props {
    params: { slug: string };
}

export async function generateStaticParams() {
    const slugs = await commerce.getAllProductSlugs();
    return slugs.map(slug => ({ slug }));
}

export const revalidate = 3600;

export default async function ProductPage({ params }: Props) {
    const product = await commerce.getProduct(params.slug);

    return (
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
            <ProductGallery images={product.images} />
            <div>
                <h1 className="text-3xl font-bold">{product.name}</h1>
                <div className="mt-4 text-2xl">{product.price} ₽</div>
                <VariantSelector variants={product.variants} />
                <AddToCartButton productId={product.id} />
            </div>
        </div>
    );
}

export async function generateMetadata({ params }: Props) {
    const product = await commerce.getProduct(params.slug);
    return {
        title: product.name,
        description: product.description.slice(0, 160),
        openGraph: {
            images: [product.images[0]?.url],
        },
    };
}

Управління станом корзини

Zustand для клієнтського стану корзини з персистентністю:

// stores/cart.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface CartState {
    cartToken: string | null;
    items: CartItem[];
    total: number;
    addItem: (productId: string, variantId?: string, qty?: number) => Promise<void>;
    removeItem: (lineId: string) => Promise<void>;
    clearCart: () => void;
}

export const useCartStore = create<CartState>()(
    persist(
        (set, get) => ({
            cartToken: null,
            items: [],
            total: 0,

            addItem: async (productId, variantId, qty = 1) => {
                let { cartToken } = get();

                if (!cartToken) {
                    const cart = await commerce.createCart();
                    cartToken = cart.token;
                    set({ cartToken });
                }

                const updatedCart = await commerce.addToCart(cartToken, {
                    productId,
                    variantId,
                    quantity: qty,
                });

                set({
                    items: updatedCart.items,
                    total: updatedCart.total,
                });
            },

            removeItem: async (lineId) => {
                const { cartToken } = get();
                if (!cartToken) return;

                const updatedCart = await commerce.removeFromCart(cartToken, lineId);
                set({ items: updatedCart.items, total: updatedCart.total });
            },

            clearCart: () => set({ cartToken: null, items: [], total: 0 }),
        }),
        { name: 'cart-storage', partialize: (state) => ({ cartToken: state.cartToken }) }
    )
);

Checkout-потік

// app/checkout/page.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { checkoutSchema, CheckoutFormData } from '@/lib/validations/checkout';
import { useCartStore } from '@/stores/cart';

export default function CheckoutPage() {
    const { cartToken, clearCart } = useCartStore();
    const { register, handleSubmit, formState: { errors } } = useForm<CheckoutFormData>({
        resolver: zodResolver(checkoutSchema),
    });

    const onSubmit = async (data: CheckoutFormData) => {
        if (!cartToken) return;

        await commerce.saveShippingAddress(cartToken, data.shipping);

        const shippingMethods = await commerce.getShippingMethods(cartToken);

        await commerce.saveShippingMethod(cartToken, shippingMethods[0].id);

        const order = await commerce.placeOrder(cartToken, data.payment);
        clearCart();
        router.push(`/orders/${order.id}/success`);
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            {/* поля форми */}
        </form>
    );
}

Продуктивність: що впливає на Core Web Vitals

Техніка LCP CLS INP
ISR для сторінок товарів +
next/image з blur placeholder + +
Попереднє завантаження шрифтів + +
Server Components для каталогу +
Skeleton-заглушки для корзини +
Prefetch для hover-станів +

Що входить в роботу

  • Шар абстракції Commerce API з документацією
  • Вітрина на Next.js з ISR для каталогу
  • Корзина на Zustand з персистентністю
  • Checkout-форма з валідацією (Zod)
  • Інтеграція пошуку (Algolia/Typesense) — опціонально
  • Налаштування SEO: структуровані дані (JSON-LD), метадані, sitemap
  • Деплой на хостинг (Vercel, AWS, Selectel) з CI/CD
  • Документація, доступи, навчання команди замовника

Отримайте консультацію щодо інтеграції — ми допоможемо обрати архітектуру та оцінимо обсяг робіт.

Термін розробки вітрини

Компонент Термін
Каталог + сторінка товару 2-3 тиж
Корзина + checkout 1-2 тиж
Особистий кабінет 1 тиж
Пошук + фільтри 1-2 тиж
Інтеграції (аналітика, пікселі) 3-5 днів
Разом 5-9 тижнів

Замовте розробку вітрини під ключ — ми оцінимо ваш проект за 1 робочий день. Зв'яжіться з нами, щоб обговорити деталі та отримати консультацію.

Розробка інтернет-магазинів

Ми знаємо: інтернет-магазин — це не просто «сайт з кошиком». Це розподілена система управління товарами, інвентарем, замовленнями, платежами, доставкою, поверненнями та комунікацією з клієнтами. Кожен блок має нетривіальну реалізацію, і більшість проблем в e-commerce виникає на стику цих підсистем. Наш досвід — понад 50 реалізованих проектів — показує, що правильна архітектура на старті економить до 40% бюджету на доробках.

Чому продуктивність каталогу деградує при зростанні SKU?

Найчастіша технічна проблема e-commerce — деградація сторінок категорій при збільшенні асортименту. Сторінка працює добре на 500 товарах і починає гальмувати на 10 000. Причини майже завжди одні й ті ж.

N+1 на атрибутах. Завантажуєте список товарів — 50 елементів. Для кожного потрібні категорія, головне фото, ціна з урахуванням знижки, наявність на складі, рейтинг. Без правильного eager loading це 250+ запитів на сторінку. В Laravel вирішується через with(['category', 'mainImage', 'currentPrice', 'stockStatus']) та withAvg('reviews', 'rating'). Але варто з'явитися персональним цінам (b2b) або складським залишкам по регіонах — і одного with() недостатньо. Потрібні Query Object або виділений ReadModel.

Фасетна фільтрація без індексів. Фільтр за кольором + розміром + брендом + діапазоном цін на таблиці в 500 000 записів без складених індексів — це seq scan при кожному запиті. PostgreSQL з правильними індексами тримає фасетну фільтрацію до кількох мільйонів товарів. Для великих каталогів — Elasticsearch або OpenSearch з агрегаціями: вони рахують кількість товарів на фільтр (facet counts) значно швидше.

Пагінація через OFFSET. LIMIT 50 OFFSET 10000 на великій таблиці — погана ідея: PostgreSQL все одно читає перші 10 050 рядків. Keyset pagination (cursor-based) через WHERE id > $last_id ORDER BY id LIMIT 50 працює за постійний час незалежно від сторінки. Як зазначено в документації PostgreSQL, cursor-based pagination гарантує O(log n) при будь-якому зміщенні, що особливо важливо для каталогів із сотнями тисяч товарів.

Конкретний кейс: каталог будівельних матеріалів, 180 000 SKU, фасетна фільтрація по 12 атрибутах. Після переходу з OFFSET-пагінації на курсорну та додавання partial index по (category_id, is_active, price) час відповіді сторінки каталогу знизився з 4,2 с до 280 мс. Економія на серверних ресурсах склала близько 30 000 ₽ на місяць. В іншому проекті (ювелірний маркетплейс) впровадження агрегацій через Elasticsearch скоротило час фільтрації з 8 до 200 мс та заощадило 50 000 ₽ на місяць на інфраструктурі — ще один приклад, як правильна архітектура знижує TCO.

Що таке race condition у кошику та як його уникнути?

Checkout — місце, де гроші або потрапляють на рахунок, або ні. Технічні проблеми тут коштують дорого.

Race condition при резервуванні товару. Два покупці одночасно додають останній екземпляр у кошик і обоє натискають «Оплатити». Без песимістичного блокування або атомарного UPDATE з перевіркою залишку обидва замовлення проходять, інвентар іде в мінус. В PostgreSQL:

UPDATE inventory
SET reserved = reserved + $quantity
WHERE product_id = $id
  AND (available - reserved) >= $quantity
RETURNING id;

Якщо RETURNING повернув 0 рядків — товару немає, показуємо помилку до списання грошей.

Ідемпотентність платіжних вебхуків. payment.succeeded від Stripe або ЮКассы може прийти двічі через мережеві збої або retry-логіку на стороні шлюзу. Без перевірки WHERE NOT EXISTS (SELECT 1 FROM processed_events WHERE event_id = $id) — дублювання замовлення або подвійне зарахування. Webhook idempotency — обов'язковий патерн для будь-якого платіжного інтегратора. Ми включаємо тест на ідемпотентність у стандартний чек-лист кожного проекту.

Checkout у кілька кроків. Multi-step checkout (адреса → доставка → оплата → підтвердження) vs single-page checkout. Дослідження показують, що single-page з прогрес-індикатором конвертує на 15–20% краще на мобільних. Стан між кроками — або localStorage + server-side сесія, або повністю server-side з проміжним збереженням. Ми гарантуємо, що кожне замовлення проходить аудит на ідемпотентність та блокування — це входить у стандартний чек-лист тестування.

Чому варто уникати CommerceML для великих каталогів CommerceML через HTTP — класична інтеграція 1С з сайтом. 1С вивантажує XML за розкладом, сайт імпортує. Для невеликих каталогів (до 5 000 SKU) це прийнятно, але при зростанні до 50 000+ SKU виникають проблеми: файл вивантаження 200 МБ кожні 30 хвилин, парсинг блокує чергу, імпорт займає 10–15 хвилин, в цей час на сайті старі ціни. Рішення — інкрементальне вивантаження (тільки зміни) та фонова обробка через Laravel Queue з кількома workers. Для високонавантажених систем ми рекомендуємо REST API або проміжну шину (RabbitMQ).

Як інтегрувати 1С з інтернет-магазином?

1С — окрема глава. Три поширених способи інтеграції:

  1. CommerceML через HTTP — 1С вивантажує XML за розкладом, сайт імпортує. Працює для невеликих каталогів, є затримка синхронізації.
  2. REST API / OData від 1С — двостороння синхронізація в реальному часі. Вимагає налаштування на стороні 1С, примхлива до версій конфігурацій.
  3. Проміжна шина (RabbitMQ / Kafka) — 1С публікує події, сайт підписується. Найнадійніший підхід для високонавантажених систем, але найдорожчий у розробці.

Служби доставки — СДЕК, Boxberry, Пошта Росії, DHL: всі надають REST API для розрахунку вартості та створення накладних. Агрегатори (Shiptor, Shipnow) дозволяють працювати з кількома службами через єдиний API.

Платіжні шлюзи

Шлюз Особливості інтеграції
Stripe Webhook-based, відмінна документація, Stripe Elements для PCI DSS
ЮКасса Популярний в РФ, підтримка ФЗ-54 (фіскалізація)
ЄРИП Білоруська система, SOAP API, специфічна документація
Tinkoff Acquiring REST API, 3D Secure 2.0, webhook-повідомлення

Для кожного шлюзу обов'язкова перевірка підпису вебхука — без цього будь-хто може відправити фейковий payment.succeeded.

CMS vs власна розробка

WooCommerce — виправданий для магазинів до ~5 000 SKU з типовою бізнес-логікою. Швидкий старт, величезна екосистема плагінів. Проблеми починаються при нестандартних цінових правилах, складних варіантах товарів або навантаженні від 10 000+ замовлень на місяць. Економія на ліцензії WooCommerce (безкоштовно) обертається витратами на плагіни та хостинг; для каталогу 50 000 SKU місячна вартість підтримки може перевищити 100 000 ₽.

OpenCart, Prestashop — аналогічна історія. Хороші для старту, обмежені при зростанні.

Власна розробка на Laravel — для:

  • Нестандартної бізнес-логіки (підписки, оренда, b2b-прайси, конфігуратор);
  • Високих вимог до продуктивності;
  • Складних інтеграцій (кілька складів, ERP, маркетплейси);
  • Унікального UX checkout.

Як ми розробляємо інтернет-магазин: покроковий процес

  1. Аналітика та проектування. Збираємо вимоги, уточнюємо бізнес-процеси, моделюємо доменну логіку. На виході — технічне завдання та архітектурна схема.
  2. Backend та API. Реалізуємо ядро (товари, кошик, замовлення), інтеграції з 1С/складами/платіжками. Використовуємо Laravel 11 з Repository pattern, чергами для асинхронних операцій.
  3. Frontend та checkout. Налаштовуємо React 18 / Next.js 14 з оптимізованим рендерингом (SSR/SSG для каталогу), єдиний single-page checkout.
  4. Тестування. Перевіряємо race condition, ідемпотентність вебхуків, навантажувальне тестування (k6), security-аудит.
  5. Деплой та моніторинг. Розгортаємо на Vercel / Docker / виділеному сервері, підключаємо Sentry та Uptime.

SEO для e-commerce

Canonical та дублювання. Фасетна фільтрація генерує тисячі URL (?color=red&size=M&sort=price). Без canonical або noindex на фільтрованих сторінках краулінговий бюджет витрачається на дублі, а основні сторінки індексуються гірше.

Structured data. Product schema з offers, aggregateRating, availability — це rich snippets у видачі: зірочки рейтингу, ціна, наявність. Впливає на CTR.

Core Web Vitals на сторінках товарів. Hero image товару — це LCP element. fetchpriority="high" на першому зображенні, правильні srcset з WebP, width та height атрибути для запобігання CLS.

Що входить у результат роботи

Після завершення проекту ви отримуєте:

  • Вихідний код та повну документацію (API, архітектура, інфраструктура);
  • Доступи до репозиторію, хостингу, моніторингу (Sentry, Uptime);
  • Навчання команди роботі з адмін-панеллю та кастомізаціями;
  • Гарантійну підтримку 3 місяці (виправлення помилок, консультації);
  • Детальний звіт по навантажувальному тестуванню та оптимізації.

Орієнтири за термінами

Тип магазину Термін
Малий (до 1 000 SKU, типова логіка) 8–12 тижнів
Середній (до 50 000 SKU, інтеграція 1С) 14–20 тижнів
Великий (100 000+ SKU, ERP, маркетплейси) 24–40 тижнів

Вартість розраховується після аналізу вимог: кількість інтеграцій, складність ціноутворення, обсяг каталогу та унікальність UX — основні фактори. Оцінимо ваш проект безкоштовно — замовте консультацію.

Чек-лист перед запуском

  • Race condition при оплаті останнього товару — покритий тестом
  • Ідемпотентність вебхуків платіжного шлюзу
  • Rate limiting на ендпоїнтах кошика та checkout
  • Canonical на фільтрованих сторінках каталогу
  • Фіскалізація чеків (ФЗ-54 для РФ або аналог)
  • Стрес-тест checkout під навантаженням (k6 або Locust)
  • Моніторинг помилок (Sentry) та алерти на payment errors
  • Backup бази даних з перевіреним restore-процесом

Гарантуємо — кожен проект проходить цей чек-лист перед релізом. Зв'яжіться з нами — підберемо оптимальну архітектуру під ваш бюджет та терміни.