Build product listing pages (Shopify Hydrogen)
Build product listing pages (Shopify Hydrogen)
Building product listing pages (PLPs) 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
ParameterManagercontroller. -
Fetching and displaying listing results by using a hook on the
ProductListcontroller. -
Wrapping your components with a listing provider so that you can manage the Headless listing state and hydration.
Prerequisite
Make sure that you understand how to build product listing pages (PLPs).
Keep the URL up to date
Keep the URL state updated in Headless to ensure that the correct product listing page (PLP) is fetched when navigating between pages.
The loader uses fetchStaticState 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.
// ...
const staticState = await fetchStaticState({
url: `https://shop.barca.group/plp/${params['*']}`,
context,
parameters,
k: 'listingEngineDefinition',
request,
});
// ...
| Specify the URL that corresponds to the PLP you want to target. |
|
|
For URL-based audiences defined in the Coveo Merchandising Hub (CMH) to function correctly with query parameters (such as campaign tracking parameters like
|
Managing URL parameters
The Headless React package provides a ParameterManager controller that lets you manage URL parameters in your commerce application.
For more information, see managing parameters.
Define and retrieve a product list controller hook
While defining your commerce engine, use the defineProductList function to define and retrieve the target product list hook.
// 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(),
// ...
},
//...
};
// ...
export const {
useProductList,
// ...
} = engineDefinition.controllers;
Define the productList controller using the defineProductList function. |
|
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 controller, and wrap your components with your listing provider.
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);
listingEngineDefinition.setNavigatorContextProvider(
() => new ServerSideNavigatorContextProvider(request),
);
const staticState = await fetchStaticState({
url: `https://shop.barca.group/plp/${params['*']}`,
context,
parameters,
k: 'listingEngineDefinition',
request,
});
return {staticState, url};
}
export default function PLP() {
const {staticState, url} = useLoaderData<typeof loader>();
const [currentUrl, setCurrentUrl] = useState(url);
useEffect(() => {
setCurrentUrl(window.location.href);
}, []);
// ...
return (
<ListingProvider
navigatorContext={new ClientSideNavigatorContextProvider()}
staticState={staticState as ListingStaticState}
>
<ParameterManager url={currentUrl} />
<section>
<div>
<div>
<Sorts />
</div>
<div ref={facetsContainer}>
<Facets numFacetsInLine={numFacetsInline} />
</div>
</div>
<Breadcrumbs />
</section>
<section>
<ProductList />
<PaginationFooter />
</section>
</ListingProvider>
);
}
Deserialize the URL parameters using the deserialize function returned by the buildParameterSerializer function. |
|
| Create a navigation context provider. | |
| Fetch the static state for the listing page with the deserialized parameters as the initial value. | |
| Wrap your components in a listing provider. | |
Use your ParameterManager component. |
|
Display the sorting options generated by the Sort controller. |
|
Display the facets generated by the FacetGenerator controller. |
|
Displays a summary of the currently active facet values via the BreadcrumbManager. |
|
Render the list of products returned by the query.
The ProductList component relies on the useProductList hook to fetch and display the products. |
|
Display the pagination generated by the Pagination controller. |
For the complete example, see the Barca Sports repository.