Интеграция Headless Commerce с Vue/Nuxt.js Storefront

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Интеграция Headless Commerce с Vue/Nuxt.js Storefront
Сложная
~2-4 недели
Часто задаваемые вопросы

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

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Интеграция Headless Commerce с Vue/Nuxt.js Storefront

Nuxt.js — зрелый фреймворк для headless e-commerce на Vue.js. Nuxt 3 с Nitro-сервером поддерживает SSR, SSG и гибридный рендеринг на уровне маршрутов. Для команд с Vue-экспертизой это прямая альтернатива Next.js с сопоставимыми возможностями.

Стек

{
    "dependencies": {
        "nuxt": "^3.10",
        "vue": "^3.4",
        "@pinia/nuxt": "^0.5",
        "@nuxtjs/i18n": "^8.0",
        "@nuxtjs/tailwindcss": "^6.0",
        "graphql-request": "^6.0",
        "@vueuse/nuxt": "^10.0"
    }
}

Конфигурация Nuxt с гибридным рендерингом

// nuxt.config.ts
export default defineNuxtConfig({
    modules: [
        '@pinia/nuxt',
        '@nuxtjs/i18n',
        '@nuxtjs/tailwindcss',
        '@vueuse/nuxt',
    ],

    // Гибридный рендеринг: каталог — статика, корзина — CSR
    routeRules: {
        '/products/**': { isr: 3600 },      // ISR каждый час
        '/categories/**': { prerender: true }, // Полная статика
        '/checkout/**': { ssr: false },       // Только клиент
        '/account/**': { ssr: false },
        '/api/**': { cors: true },
    },

    runtimeConfig: {
        commerceApiSecret: process.env.COMMERCE_API_SECRET,
        public: {
            commerceApiUrl: process.env.NUXT_PUBLIC_COMMERCE_API_URL,
            typesenseApiKey: process.env.NUXT_PUBLIC_TYPESENSE_KEY,
        },
    },
});

Commerce Composable

В Nuxt 3 логика работы с API инкапсулируется в composable:

// composables/useCommerce.ts
import { GraphQLClient } from 'graphql-request';
import type { Product, Category, Cart } from '~/types/commerce';

export const useCommerce = () => {
    const config = useRuntimeConfig();

    const client = new GraphQLClient(config.public.commerceApiUrl, {
        headers: { 'Accept': 'application/json' },
    });

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

    const getProducts = async (params: ProductsParams) => {
        const data = await client.request(GET_PRODUCTS_QUERY, params);
        return {
            items: data.products.data.map(normalizeProduct),
            pagination: data.products.paginatorInfo,
        };
    };

    const getCategories = async (): Promise<Category[]> => {
        const { categories } = await client.request(GET_CATEGORIES_QUERY);
        return categories;
    };

    return { getProduct, getProducts, getCategories };
};

Страница товара

<!-- pages/products/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { getProduct } = useCommerce();

const { data: product, error } = await useAsyncData(
    `product-${route.params.slug}`,
    () => getProduct(route.params.slug as string),
    { server: true }
);

if (error.value) throw createError({ statusCode: 404, message: 'Товар не найден' });

// SEO
useSeoMeta({
    title: () => product.value?.name,
    description: () => product.value?.description?.slice(0, 160),
    ogImage: () => product.value?.images[0]?.url,
});

useSchemaOrg([
    defineProduct({
        name: () => product.value?.name ?? '',
        sku: () => product.value?.sku ?? '',
        offers: defineOffer({
            price: () => product.value?.price ?? 0,
            priceCurrency: 'RUB',
        }),
    }),
]);
</script>

<template>
    <div v-if="product" class="grid grid-cols-1 lg:grid-cols-2 gap-12">
        <ProductGallery :images="product.images" />
        <div>
            <h1 class="text-3xl font-bold">{{ product.name }}</h1>
            <ProductPrice :price="product.price" :compare-at="product.compareAtPrice" />
            <VariantSelector :variants="product.variants" />
            <AddToCartButton :product-id="product.id" />
        </div>
    </div>
</template>

Pinia Store для корзины

// stores/cart.ts
import { defineStore } from 'pinia';

export const useCartStore = defineStore('cart', () => {
    const cartToken = useCookie<string>('cart_token', {
        maxAge: 60 * 60 * 24 * 30, // 30 дней
        sameSite: 'strict',
    });

    const items = ref<CartItem[]>([]);
    const total = ref(0);
    const loading = ref(false);

    const commerce = useCommerce();

    const addItem = async (productId: string, variantId?: string, qty = 1) => {
        loading.value = true;
        try {
            if (!cartToken.value) {
                const cart = await commerce.createCart();
                cartToken.value = cart.token;
            }

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

            items.value = updatedCart.items;
            total.value = updatedCart.total;
        } finally {
            loading.value = false;
        }
    };

    const removeItem = async (lineId: string) => {
        if (!cartToken.value) return;
        const updatedCart = await commerce.removeFromCart(cartToken.value, lineId);
        items.value = updatedCart.items;
        total.value = updatedCart.total;
    };

    const itemCount = computed(() =>
        items.value.reduce((sum, item) => sum + item.quantity, 0)
    );

    return { cartToken, items, total, loading, itemCount, addItem, removeItem };
}, {
    persist: { storage: persistedState.cookiesWithOptions({ sameSite: 'strict' }) },
});

Каталог с фильтрами

<!-- pages/categories/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { getProducts, getCategories } = useCommerce();

const page = ref(1);
const selectedFilters = ref<Record<string, string[]>>({});

const { data, pending, refresh } = await useAsyncData(
    `category-${route.params.slug}-${page.value}`,
    () => getProducts({
        category: route.params.slug as string,
        page: page.value,
        filters: selectedFilters.value,
        perPage: 24,
    }),
    { watch: [page, selectedFilters] }
);

const updateFilter = (code: string, value: string) => {
    const current = selectedFilters.value[code] ?? [];
    selectedFilters.value = {
        ...selectedFilters.value,
        [code]: current.includes(value)
            ? current.filter(v => v !== value)
            : [...current, value],
    };
    page.value = 1;
};
</script>

<template>
    <div class="flex gap-8">
        <FilterSidebar
            :filters="data?.filters"
            :selected="selectedFilters"
            @update="updateFilter"
        />
        <div>
            <ProductGrid :products="data?.items" :loading="pending" />
            <Pagination v-model="page" :total-pages="data?.pagination.lastPage" />
        </div>
    </div>
</template>

Интернационализация

// i18n.config.ts
export default defineI18nConfig(() => ({
    legacy: false,
    locale: 'ru',
    fallbackLocale: 'en',
}));
// nuxt.config.ts (i18n секция)
i18n: {
    locales: [
        { code: 'ru', iso: 'ru-RU', file: 'ru.json' },
        { code: 'en', iso: 'en-US', file: 'en.json' },
    ],
    defaultLocale: 'ru',
    strategy: 'prefix_except_default',
    detectBrowserLanguage: {
        useCookie: true,
        cookieKey: 'locale',
    },
},

Подключение к Bagisto GraphQL

// server/api/products/[slug].ts
// Серверный маршрут Nuxt — проксирование с добавлением заголовков
export default defineEventHandler(async (event) => {
    const slug = getRouterParam(event, 'slug');
    const config = useRuntimeConfig();

    const data = await $fetch(config.commerceApiUrl, {
        method: 'POST',
        body: {
            query: `
                query GetProduct($slug: String!) {
                    product(urlKey: $slug) {
                        id sku name description
                        price priceHtml { finalPrice }
                        images { path }
                        categories { id name slug }
                    }
                }
            `,
            variables: { slug },
        },
        headers: {
            'Authorization': `Bearer ${config.commerceApiSecret}`,
        },
    });

    return data.data.product;
});

Сравнение Next.js и Nuxt.js для e-commerce

Аспект Next.js Nuxt.js
Экосистема Больше готовых компонентов Богаче Vue-экосистема
TypeScript Отличная поддержка Отличная поддержка
SSG/ISR App Router + ISR routeRules + ISR (Nitro)
Деплой Vercel (нативно) Vercel, Netlify, Node
Composables vs Hooks React Hooks Vue Composables (удобнее)
Реактивность useState + useEffect ref/computed (проще)

Срок разработки

Модуль Срок
Настройка проекта, роутинг, i18n 3-5 дней
Каталог + страница товара 2-3 нед
Корзина + Checkout 1-2 нед
Личный кабинет 1 нед
Поиск (Typesense/Algolia) 3-5 дней
Итого 5-8 недель