---
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 (
{state.products.map((product) => (
))}
);
}
```
<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 (
<>
{state.headline}
{state.products.map((product) => (
))}
>
);
}
```
> **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).