Alokai
You are reading the documentation for v0, which is no longer the latest version. Switch to v1

Frontend Modification

Frontend

There are a few manual steps required to integrate the Coveo module into your storefront. We will guide you through the process of integrating the module into your storefront. In most cases, you will need to replace the existing component with the Coveo component.

Environment variables

Add environment variables in your .env file:

NEXT_PUBLIC_COVEO_ORGANIZATION_ID=xxxxyyyyzzzz
NEXT_PUBLIC_COVEO_ACCESS_TOKEN=xxx-yyyy-zzz-www-cccccccccc
NEXT_PUBLIC_COVEO_TRACKING_ID=xxxx-yyyy-zzzz
NUXT_PUBLIC_COVEO_ORGANIZATION_ID=xxxxyyyyzzzz
NUXT_PUBLIC_COVEO_ACCESS_TOKEN=xxx-yyyy-zzz-www-cccccccccc
NUXT_PUBLIC_COVEO_TRACKING_ID=xxxx-yyyy-zzzz

Client instance provider

Add a CoveoClientInstanceProvider to the Providers component.

// apps/storefront-unified-nextjs/components/providers.tsx

// ...
import { CoveoClientInstanceProvider } from '@/sf-modules/coveo';
// ...

export default function Providers({ children, initialCurrency, sdkOptions }: ProvidersProps) {
  // ...
  return (
    // ...  
    <CartProvider>
      <CoveoClientInstanceProvider>
        <StateObserver>{children}</StateObserver> 
      </CoveoClientInstanceProvider>
    </CartProvider>
    // ...  
  );
}

In Nuxt this step is not needed.

Import the CoveoSearch component and replace the existing search component in the template. It will enable Quick Search with suggestions from Coveo.

// apps/storefront-unified-nextjs/app/[locale]/(default)/components/navbar.tsx

// ...
import Search from './search'; 
import { CoveoSearch } from '@sf-modules/coveo'; 
// ...

return (
  <>
    {/* ... */}
    <Search className="hidden flex-1 md:block" placeholder={t('search')} />
    <CoveoSearch className="hidden flex-1 md:block" placeholder={t('search')} />
    {/* ... */}
    <Search placeholder={t('search')} />
    <CoveoSearch placeholder={t('search')} />
    {/* ... */}
  </>
)

Add translation namespace inside NextIntlClientProvider.

// storefront-unified-nextjs/app/[locale]/layout.tsx

<NextIntlClientProvider
  messages={pick(messages,
    'Notifications.NotificationAlert',
    'SapAsm',
    'CartProductCard'
  )}
>
  // ...
</NextIntlClientProvider>
// storefront-unified-nuxt/components/ui/Header.vue

<template>
  <!-- ... -->
  <UiSearch class="hidden md:block flex-1" />
  <CoveoSearch class="hidden md:block flex-1" />
  <!-- ... -->
  <UiSearch :close="searchModalClose" />
  <CoveoSearch :close="searchModalClose" />
  <!-- ... -->
</template>

<script setup lang="ts">
import { CoveoSearch } from '@sf-modules/coveo'; 
// ...
</script>

Search results and category pages

Search results and category pages are separate pages but the required steps are similar. You need to replace the useSearchProducts with useCoveoSearchProducts. It will enable AI-Powered Search Results and Dynamic Faceting.

Category Page example.

// storefront-unified-nextjs/app/[locale]/(default)/category/[[...slugs]]/page.tsx
import { useCoveoSSR } from '@/sf-modules/coveo/hooks/use-coveo-ssr'; 
import { headers } from 'next/headers'; 
import CoveoClientProvider from '@/sf-modules/coveo/components/coveo-client-provider'; 
// ...

export default connectCmsPage<CategoryPageProps>(
  async (props) => {
    // ...
    const [categoryData, productCatalog] = await Promise.all([ 
      getCategory(categoryId), 
      sdk.unified.searchProducts(searchProductsQuery), 
    ]); 
    const url = new URL(headers().get('x-current-path') as string); 
    const [categoryData, coveoResult] = await Promise.all([getCategory(categoryId), useCoveoSSR(url)]); 
    const { staticState, updatedState: productCatalog } = coveoResult; 

    // ...
    return (
      <CoveoClientProvider searchData={productCatalog} staticState={staticState}>
        // ...
      </CoveoClientProvider>
    )

Search Page example.

// storefront-unified-nextjs/app/[locale]/(default)/search/page.tsx
import { useCoveoSSR } from '@sf-modules/coveo/hooks/use-coveo-ssr'; 
import { headers } from 'next/headers'; 
import CoveoClientProvider from '@/sf-modules/coveo/components/coveo-client-provider'; 
// ...

export default async function SearchPage({ searchParams }: SearchPageProps) {
  // ...
  const productCatalog = await sdk.unified.searchProducts(searchProductsQuery); 
  const url = new URL(headers().get('x-current-path') as string); 
  const { staticState, updatedState: productCatalog } = await useCoveoSSR(url); 

  // ...
  return (
    <CoveoClientProvider searchData={productCatalog} staticState={staticState}>
      // ...
    </CoveoClientProvider>
  )

Middleware modification, need to add x-current-path header for frontend.

// storefront-unified-nextjs/middleware.ts

export default async function middleware(request: NextRequest) {
  // ...

  // Add a new header x-current-path which passes the path to downstream components
  response.headers.set('x-current-path', request.url);  

  return response;
}
  • Replace useLazySearchProducts with useCoveoSearchProducts; pay attention to the true parameter
  • Remove the :is-lazy-loading-data="lazyLoading" from CategoryPageContent component
// storefront-unified-nuxt/pages/search.vue

<template>
  // ...
  <CategoryPageContent
    v-if="productsCatalog"
    :title="$t('resultsFor', { phrase: query.search })"
    :total-products="productsCatalog.pagination.totalResults"
    :products="productsCatalog.products"
    :is-lazy-loading-data="lazyLoading"
  >
  // ...
</template>

<script setup lang="ts">
import { useCoveoSearchProducts } from "@sf-modules/coveo"; 
//...
const {
  setPageInQuery,
  fetchProducts,
  watchQuery,
  query,
  data: productsCatalog,
  lazyLoading
} = useLazySearchProducts(); 
} = useCoveoSearchProducts(true); 
//...
</script>
  • Replace useSearchProducts with useCoveoSearchProducts, pay attention to the true parameter
  • Remove the :is-lazy-loading-data="lazyLoading" from CategoryPageContent component
// storefront-unified-nuxt/pages/category/[...slugs].vue

<template>
  <!-- ... -->
  <CategoryPageContent
    v-if="productsCatalog"
    :title="$t('resultsFor', { phrase: query.search })"
    :total-products="productsCatalog.pagination.totalResults"
    :products="productsCatalog.products"
    :is-lazy-loading-data="lazyLoading"
  >
</template>

<script setup lang="ts">
import { useCoveoSearchProducts } from "@sf-modules/coveo"; 
//...
const {
  setPageInQuery,
  fetchProducts,
  watchQuery,
  data: productsCatalog,
  lazyLoading, 
} = useLazySearchProducts(); 
} = useCoveoSearchProducts(true); 
//...
</script>
  • Remove cache related code from CategoryPageContent component, Coveo results are personalized and should not be cached.
if (Object.keys(route.query).length === 0) {
  // We cache only base category pages
  // Please note, that this page should not be cached if the results are coming from personalized recommendations
  const { setCacheControl } = useCacheControl();
  setCacheControl();
}

Category Filters

Coveo module is shipped with additional filters components. You need to add or replace the existing filters with the Coveo filters.

// storefront-unified-nextjs/components/products-listing/facets.tsx
import CoveoPriceItem from '@sf-modules/coveo/components/coveo-filter-price'; 
// ...

return (
  // ...
   case 'PRICE': { 
    return <Facet facet={facet} itemRenderer={CoveoPriceItem} key={facet.name} />; 
  } 
  // ...
)

Add CoveoFilterPrice component.

// storefront-unified-nuxt/components/CategoryFilters/Filter.vue

<template>
  <UiAccordionItem v-if="facet" v-model="open" class="border-b border-neutral-200">
    <!-- ... -->
    <template v-else-if="facet.type === 'PRICE'">
      <CoveoFilterPrice
        v-for="facetValue in itemsToDisplay"
        :key="facetValue.value"
        :facet-value="facetValue"
        v-model:selected="selectedProxy"
        :active="isItemActive(facetValue.value)"
      />
    </template>
    <!-- ... -->
  </UiAccordionItem>
</template>

<script setup lang="ts">
import { CoveoFilterPrice } from '@sf-modules/coveo'; 
</script>

Category Page Content

To enable the AI-Powered Search Results you will also need to add Product Card components that will be used to send the click events to Coveo.

// storefront-unified-nextjs/components/products-listing/products-list.tsx
import CoveoProductCardVertical from '@/sf-modules/coveo/components/coveo-product-card-vertical'; 
// ...

export default async function ProductListPage({ products }: ProductsListProps) { 
export default function ProductListPage({ products }: ProductsListProps) { 
  // ...

  return products.map(({ id, name: productTitle, price, primaryImage, quantityLimit, rating, sku, slug }, index) => (
    <ProductCardVertical
      oldPrice={price?.isDiscounted ? price.regularPrice : undefined}
    <CoveoProductCardVertical

Add i18n provider with namespace into products-listing-page.tsx file.

export default function ProductsListingPage({
  // ...

  return (
    // ...
    <NextIntlClientProvider messages={pick(messages, 'AddToCartButton')}>
      <ProductsList products={products} />
    </NextIntlClientProvider> 
    // ...
  )
// storefront-unified-nuxt/components/CategoryPageContent/types.ts
import type { SfProductCatalogItem } from '~/types'; 
import type { NormalizedSSRResult } from '@sf-modules/coveo'; 

export type CategoryPageContentProps = {
  title: string;
  totalProducts: number;
  itemsPerPage?: number;
  products?: SfProductCatalogItem[]; 
  products?: NormalizedSSRResult[]; 
};
// storefront-unified-nuxt/components/CategoryPageContent/CategoryPageContent.vue
<script>
import { CoveoProductCard } from '@sf-modules/coveo'; 
// ...
</script>

<template>
  <UiProductCard
    {{! ... }}
  />
  <CoveoProductCard
    v-for="({ id, sku, name, rating, price, primaryImage, slug, quantityLimit, result }, index) in products"
    :coveo-result="result"
    {{! ... }}
  />
</template>

Other

You should also replace the useSearchProducts with useCoveoSearchProducts in other components that are using it.

Keep the original destruct properties unless you want to change the behavior of the component or the composable.

List of components that are using useSearchProducts:

Hook is used in another hooks, no component use it directly.

  • storefront-unified-nuxt/components/CategoryFilters/CategoryFilters.vue
  • storefront-unified-nuxt/components/CategorySidebar/CategorySidebar.vue
  • storefront-unified-nuxt/components/CategorySorting/CategorySorting.vue
  • storefront-unified-nuxt/composables/useFacet/useFacet.ts
<script setup lang="ts">
import { useCoveoSearchProducts } from "@sf-modules/coveo";
// ...
const { original_destruct_properties } = useCoveoSearchProducts();
// ...
</script>

VSCode users

It is possible that type inference for value returned from useNuxtApp won't work correctly for VSCode users. In order to fix it, you need to update TypeScript version, to be derived from workspace. You can read more about it here. It's not only related to Volar but also to the new Vue Official VSCode extension.

On this page