--- title: Build product listing pages (SSR) slug: p25b0072 canonical_url: https://docs.coveo.com/en/p25b0072/ collection: coveo-for-commerce source_format: adoc --- # Build product listing pages (SSR) > **Important** > > The Headless Commerce SSR utilities are in open beta. > Contact your Coveo representative for support in adopting this. Building [product listing pages (PLPs)](https://docs.coveo.com/en/m1sf3187/) with Coveo Headless involves three main elements: . Keeping the URL up to date with the Headless state as you navigate between pages using a hook on the [`ParameterManager`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.ParameterManager.html) controller. . Fetching and displaying listing results using a hook on the [`ProductList`](https://docs.coveo.com/en/headless-react/latest/reference/types/SSR_Commerce.index.ProductList.html) controller. . Wrapping your components with a listing [provider](https://docs.coveo.com/en/obif0156#create-providers) to manage the Headless listing state and [hydration](https://docs.coveo.com/en/p2ae0404/). A complete example of a listing page component is available in the [sample project in the Headless repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/(listing)/%5Bcategory%5D/page.tsx). ## Prerequisite Make sure that you understand how to [build product listing pages (PLPs)](https://docs.coveo.com/en/o4ue0471/). ## Keep the URL up to date Keep the URL state updated in Headless to ensure that the correct [product listing page (PLP)](https://docs.coveo.com/en/m1sf3187/) is fetched when navigating between pages. The `loader` uses [`fetchStaticState`](https://docs.coveo.com/en/p3ae0283#fetch-static-headless-state) to sync the URL state with the interface. It's crucial to pass the correct URL to fetch products, because it's assigned to `context.view` and associated with the PLP. ```tsx // ... const staticState = await listingEngineDefinition.fetchStaticState({ controllers: { cart: {initialState: [items](https://docs.coveo.com/en/210/)}, context: { language: defaultContext.language, country: defaultContext.country, currency: defaultContext.currency, view: { url: `https://sports.barca.group/browse/promotions/${category}`, <1> }, }, parameterManager: {initialState: {parameters}}, }, }); // ... ``` <1> Specify the URL that corresponds to the PLP you want to target. > **Important** > > For URL-based audiences to function correctly with query parameters (such as campaign tracking parameters like `?brand=barca&model=sp&color=red`), you must include query or hash parameters in the URL passed to `fetchStaticState`: > > ```tsx const url = new URL(request.url); const staticState = await listingEngineDefinition.fetchStaticState({ controllers: { context: { view: { url: `url.href`, <1> }, }, }, }); ``` > <1> Include query parameters to ensure the Commerce API receives the complete URL for audience targeting. ### Managing URL parameters The Headless React package provides a [`ParameterManager`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.ParameterManager.html) controller that lets you manage URL parameters in your commerce application. For more information, see [managing parameters](https://docs.coveo.com/en/p23d7042/). ## Define and retrieve a product list controller hook When [defining your commerce engine](https://docs.coveo.com/en/obif0156#define-the-commerce-engine-and-controllers), leverage the [`defineProductList`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineProductList.html) function to define and retrieve the target product list hook. ```tsx // lib/commerce-engine-config.ts import { defineProductList, // ... } from '@coveo/headless-react/ssr-commerce'; export default { // ... controllers: { productList: defineProductList(), // ... }, // ... }; ``` 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, // ... } = engineDefinition.controllers; ``` > **Note** > > You can use the same `useProductList` hook for both search and listing pages. ### Display listing page results with the listing provider Render your listing page results using your hook on the [`ProductList`](https://docs.coveo.com/en/headless-react/latest/reference/types/SSR_Commerce.index.ProductList.html) controller, and wrap your components with your listing provider. ```tsx // app/(listing)/[category]/page.tsx import ContextDropdown from '@/components/context-dropdown'; import FacetGenerator from '@/components/facets/facet-generator'; import Pagination from '@/components/pagination'; import ParameterManager from '@/components/parameter-manager'; import ProductList from '@/components/product-list'; import { ListingProvider } from '@/components/providers/providers'; import Sort from '@/components/sort'; import StandaloneSearchBox from '@/components/standalone-search-box'; import {listingEngineDefinition} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; import {headers} from 'next/headers'; import {notFound} from 'next/navigation'; export default async function Listing({ <1> params, searchParams, }: { params: {category: string}; searchParams: Promise; }) { const {category} = params; const navigatorContext = new NextJsNavigatorContext(headers()); <2> listingEngineDefinition.setNavigatorContextProvider(() => navigatorContext); const {deserialize} = buildParameterSerializer(); const parameters = deserialize(await searchParams); const items = // ... <3> const staticState = await listingEngineDefinition.fetchStaticState({ controllers: { cart: {initialState: [items](https://docs.coveo.com/en/210/)}, context: { language: defaultContext.language, country: defaultContext.country, currency: defaultContext.currency, view: { url: `https://sports.barca.group/browse/promotions/${category}`, }, }, parameterManager: {initialState: {parameters}}, }, }); return ( staticState={staticState} navigatorContext={navigatorContext.marshal} > <5> <6>
<7>
<8> <9> <10>
); } export const dynamic = 'force-dynamic'; ``` <1> Retrieve the [search parameters](https://nextjs.org/docs/app/api-reference/file-conventions/page) from the URL. <2> [Create a navigation context provider](https://docs.coveo.com/en/obif0156#create-a-navigation-context-provider). <3> Retrieve [cart items](https://docs.coveo.com/en/oc685394/). <4> Wrap your components with your listing [provider](https://docs.coveo.com/en/obif0156#create-providers). <5> Use your [parameter manager component](https://docs.coveo.com/en/oc685395/). <6> Let your users [change context](https://docs.coveo.com/en/obif0156#create-a-navigation-context-provider). <7> [Display the facets](https://docs.coveo.com/en/oc685396#facet-generator) generated by the `FacetGenerator` controller. <8> [Display the sorting options](https://docs.coveo.com/en/oc685396#sorting) generated by the `Sort` controller. <9> [Render the list of `products`](https://docs.coveo.com/en/oc685393/) returned by the query. The `ProductList` component relies on the `useProductList` hook to fetch and display the products. <10> Display the [pagination](https://docs.coveo.com/en/oc685396#pagination) generated by the `Pagination` controller. A complete example of how to build a PLP is available in the [Headless repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/(listing)/%5Bcategory%5D/page.tsx). Within the sample project, you can find additional components that were omitted in this article, such as the [`BreadcrumbManager`](https://docs.coveo.com/en/headless-react/latest/reference/types/SSR_Commerce.index.BreadcrumbManager.html) (displays a summary of the currently active facet values) and [`Summary`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.Summary.html) (provides a summary of search results such as the number of results returned).