--- title: Build product listing pages (Shopify Hydrogen) slug: p3bb8107 canonical_url: https://docs.coveo.com/en/p3bb8107/ collection: coveo-for-commerce source_format: adoc --- # Build product listing pages (Shopify Hydrogen) 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 by 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 by 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/p3ae0283#headless-specific-providers) so that you can manage the Headless listing state and [hydration](https://docs.coveo.com/en/p2ae0404/). ## 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 fetchStaticState({ url: `https://shop.barca.group/plp/${params['*']}`, <1> context, parameters, k: 'listingEngineDefinition', request, }); // ... ``` <1> Specify the URL that corresponds to the PLP you want to target. > **Important** > > For URL-based audiences defined in the [Coveo Merchandising Hub (CMH)](https://docs.coveo.com/en/o5290573/) 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 fetchStaticState({ url: `url.href`, <1> context, parameters, k: 'listingEngineDefinition', request, }); ``` > <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 While [defining your commerce engine](https://docs.coveo.com/en/p1oe0067#define-the-commerce-engine-and-controllers), use 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. ```ts // app/lib/commerce-engine-config.ts import { defineCommerceEngine } from '@coveo/headless-react/ssr-commerce'; import { defineProductList, // ... } from '@coveo/headless-react/ssr-commerce'; // ... export default { // ... controllers: { productList: defineProductList(), <1> // ... }, //... }; // ... export const { useProductList, <2> // ... } = engineDefinition.controllers; ``` <1> Define the `productList` controller using the `defineProductList` function. <2> 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 import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, useParams, type MetaFunction} from '@remix-run/react'; import { ClientSideNavigatorContextProvider, ServerSideNavigatorContextProvider, } from '~/lib/navigator.provider'; import { engineDefinition, fetchStaticState, type ListingStaticState, } from '~/lib/coveo.engine'; import {ListingProvider} from '~/components/Search/Context'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; import {useEffect, useState} from 'react'; import ParameterManager from '~/components/ParameterManager'; // ... export async function loader({context, params, request}: LoaderFunctionArgs) { const {listingEngineDefinition} = engineDefinition; const url = new URL(request.url); const {deserialize} = buildParameterSerializer(); const parameters = deserialize(url.searchParams); <1> listingEngineDefinition.setNavigatorContextProvider( <2> () => new ServerSideNavigatorContextProvider(request), ); const staticState = await fetchStaticState({ <3> url: `https://shop.barca.group/plp/${params['*']}`, context, parameters, k: 'listingEngineDefinition', request, }); return {staticState, url}; } export default function PLP() { const {staticState, url} = useLoaderData(); const [currentUrl, setCurrentUrl] = useState(url); useEffect(() => { setCurrentUrl(window.location.href); }, []); // ... return ( navigatorContext={new ClientSideNavigatorContextProvider()} staticState={staticState as ListingStaticState} > <5>
<6>
<7>
<8>
<9> <10>
); } ``` <1> Deserialize the URL parameters using the `deserialize` function returned by the [`buildParameterSerializer`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.buildParameterSerializer.html) function. <2> Create a [navigation context provider](https://docs.coveo.com/en/p3ae0283#navigation-context-providers). <3> [Fetch the static state](https://docs.coveo.com/en/p3ae0283#fetch-static-headless-state) for the listing page with the deserialized parameters as the initial value. <4> Wrap your components in a listing [provider](https://docs.coveo.com/en/p3ae0283#headless-specific-providers). <5> Use your [`ParameterManager`](https://docs.coveo.com/en/p23d7042/) component. <6> [Display the sorting options](https://docs.coveo.com/en/p25e0438#sorting) generated by the `Sort` controller. <7> [Display the facets](https://docs.coveo.com/en/p25e0438#facets) generated by the `FacetGenerator` controller. <8> Displays a summary of the currently active facet values via the [`BreadcrumbManager`](https://docs.coveo.com/en/headless-react/latest/reference/types/SSR_Commerce.index.BreadcrumbManager.html). <9> [Render the list of products](https://docs.coveo.com/en/p24a0093/) 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/p25e0438#pagination) generated by the `Pagination` controller. For the complete example, see the [Barca Sports repository](https://github.com/coveo-labs/barca-sports-hydrogen/blob/main/app/routes/(%24locale).plp.%24.tsx).