--- title: Displaying products (SSR) slug: oc685393 canonical_url: https://docs.coveo.com/en/oc685393/ collection: coveo-for-commerce source_format: adoc --- # Displaying products (SSR) > **Important** > > The Headless Commerce SSR utilities are in open beta. > Contact your Coveo representative for support in adopting this. Regardless of the [product discovery solution](https://docs.coveo.com/en/o9cf0524/) you're building (search, listings, or recommendations), displaying products to the user is an essential part of the commerce experience. Products can be displayed either as part of a list of products or as a single product on a [product detail page (PDP)](https://docs.coveo.com/en/n8ad7392/). This article explains how to display products and ensure that the correct events, such as [clicks](https://docs.coveo.com/en/o1n92447/) and [product views](https://docs.coveo.com/en/o1n93101/), are logged when a user interacts with a product. ## Displaying lists of products In your commerce [storefront](https://docs.coveo.com/en/p33g0410/), you'll often need to display a list of products, for instance when showing search results, product recommendations, or [product listing pages](https://docs.coveo.com/en/m1sf3187/). It's important to log [click events](https://docs.coveo.com/en/o1n92447/) when a user interacts with a product in these lists. ### Accessing products through controller hooks Controller hooks let you interact with products and display them in your storefront. #### Define target controller hooks [Define and use controller hooks](https://docs.coveo.com/en/obif0156#define-the-commerce-engine-and-controllers) specific to the product discovery solution you're implementing. [cols="1,3"] |=== | Context | Function to use to define the controller hook | Search page | [`defineProductList`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineProductList.html) | Listing page | [`defineProductList`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineProductList.html) | Recommendation interface | [`defineRecommendations`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineRecommendations.html) |=== Commerce engine configuration example: ```tsx // lib/commerce-engine-config.ts import { defineProductList, defineRecommendations, // ... } from '@coveo/headless-react/ssr-commerce'; export default { // ... controllers: { productList: defineProductList(), popularViewed: defineRecommendations({ options: { slotId: 'd73afbd2-8521-4ee6-a9b8-31f064721e73', }, }), popularBought: defineRecommendations({ options: { slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', }, }), viewedTogether: defineRecommendations({ options: { slotId: 'ff5d8804-d398-4dd5-b68c-6a729c66454b', }, }), // ... }, // ... }; ``` Commerce engine definition example: ```tsx // lib/commerce-engine.ts import {defineCommerceEngine} from '@coveo/headless-react/ssr-commerce'; import engineConfig from './commerce-engine-config'; export const engineDefinition = defineCommerceEngine(engineConfig); // ... export const { useProductList, usePopularBought, usePopularViewed, useViewedTogether, // ... } = engineDefinition.controllers; ``` For complete code samples, see [commerce-engine-config.ts](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/lib/commerce-engine-config.ts) and [commerce-engine.ts](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/lib/commerce-engine.ts). #### Access products with target hooks Regardless of the controller hook you use, you can display products by accessing the `state.products` object on the controller hook. This object contains a list of products (`Product[]`) that you can render to the user. Once you've retrieved one of the controller hooks listed above and have access to `state.products`, you can pass the products to a component that will render them. The following example `ProductList` component receives a list of products and renders them using a `ProductButtonWithImage` component, defined in the [following section](#display-the-target-product). It's meant to work for product listing search pages. ```tsx // components/product-list.tsx 'use client'; import { useProductList // ... } from '@/lib/commerce-engine'; import ProductButtonWithImage from './product-button-with-image'; // ... export default function ProductList() { const {state, methods} = useProductList(); // ... return ( ); } ``` <1> The logic to interact with the cart and log events based on user interaction has been omitted to keep this code simple. For a complete sample, see the [Headless project repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/components/product-list.tsx). To display recommendations, you would use the `usePopularBought`, `usePopularViewed`, or `useViewedTogether` controller hooks instead of `useProductList`. ```tsx // components/recommendations/popular-bought.tsx 'use client'; import {usePopularBought} from '@/lib/commerce-engine'; import ProductButtonWithImage from './product-button-with-image'; export default function PopularBought() { const {state, methods} = usePopularBought(); return ( <> ); } ``` > **Note** > > Also, to display recommendations, make sure to enable the target recommendation hook when fetching the static state. > See [Display recommendations with your recommendation provider](https://docs.coveo.com/en/p25b0248#display-recommendations-with-your-recommendation-provider). #### Display the target product Whether it's on a search page, a product listing page, a recommendation carousel, or as an instant product displayed below the search box, every product displayed in a product list must use the product [`interactiveProduct.select`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.InteractiveProduct.html#select-1) function to ensure that the correct analytics event is logged when the product is clicked. ```tsx // components/product-button-with-image.tsx import { Product, ProductList, Recommendations, } from '@coveo/headless-react/ssr-commerce'; import Image from 'next/image'; import {useRouter} from 'next/navigation'; export interface ProductButtonWithImageProps { methods: <1> | Omit | Omit | undefined; product: Product; } export default function ProductButtonWithImage({ methods, product, }: ProductButtonWithImageProps) { const router = useRouter(); const onProductClick = (product: Product) => { methods?.interactiveProduct({options: {product}}).select(); router.push( `/products/${product.ec_product_id}?name=${product.ec_name}&price=${product.ec_price}` ); }; return ( ); } ``` <1> The `methods` prop is a union of the methods available through the `Recommendations` and `ProductList` controller hooks. This lets the component work for both use cases. The `undefined` type is for the case of static rendering, before [hydration](https://docs.coveo.com/en/p2ae0404/), when the hook isn't yet available. ## Displaying a product on a product detail page When displaying a product on a [product detail page (PDP)](https://docs.coveo.com/en/n8ad7392/), define and use a hook on the [`ProductView`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineProductView.html) controller to ensure that the correct [product view event](https://docs.coveo.com/en/o1n93101/) is logged when a user views a product. Create a product viewer component that you'll then include in your product detail pages. ```tsx // components/product-viewer.tsx 'use client'; import {useProductView} from '@/lib/commerce-engine'; import {useEffect} from 'react'; interface Product { productId: string; name: string; price: number; } export default function ProductViewer({productId, name, price}: Product) { const {methods} = useProductView(); const productViewEventEmitted = false; <1> useEffect(() => { if (methods && !productViewEventEmitted) { methods?.view({productId, name, price}); productViewEventEmitted = true; } }, []); return null; } ``` <1> A flag so the product view event is logged only once. Then, include the `ProductViewer` component in your product detail page component. ```tsx // app/products/[productId].page.tsx // ... import ProductViewer from '@/components/product-viewer'; export default async function ProductDescriptionPage({ params, searchParams, }: { params: {productId: string}; searchParams: Promise<{[key: string]: string | string[] | undefined}>; }) { // ... const resolvedSearchParams = await searchParams; const price = Number(resolvedSearchParams.price) ?? NaN; const name = Array.isArray(resolvedSearchParams.name) ? params.productId : (resolvedSearchParams.name ?? params.productId); return ( staticState={staticState} navigatorContext={navigatorContext.marshal} > ); } export const dynamic = 'force-dynamic'; ``` <1> Wrap your product viewer component in a `StandaloneProvider` to handle static rendering and [hydration](https://docs.coveo.com/en/p2ae0404/). See [Headless commerce usage (SSR): Create providers](https://docs.coveo.com/en/obif0156#create-providers).