Інтеграція 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 інкапсульована в composables:

// 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: 'UAH',
        }),
    }),
]);
</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 };
});

Каталог з фільтрами

<!-- pages/categories/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { getProducts } = 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: 'uk',
    fallbackLocale: 'en',
}));
// nuxt.config.ts (i18n секція)
i18n: {
    locales: [
        { code: 'uk', iso: 'uk-UA', file: 'uk.json' },
        { code: 'en', iso: 'en-US', file: 'en.json' },
    ],
    defaultLocale: 'uk',
    strategy: 'prefix_except_default',
}

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

Модуль Термін
Налаштування проекту, роутинг, i18n 3-5 днів
Каталог + сторінка товара 2-3 тижні
Кошик + Checkout 1-2 тижні
Особистий кабінет 1 тиждень
Пошук (Typesense/Algolia) 3-5 днів
Разом 5-8 тижнів