--- title: Managing parameters (SSR) slug: oc685395 canonical_url: https://docs.coveo.com/en/oc685395/ collection: coveo-for-commerce source_format: adoc --- # Managing parameters (SSR) > **Important** > > The Headless Commerce SSR utilities are in open beta. > Contact your Coveo representative for support in adopting this. When building your [server-side rendered (SSR)](https://docs.coveo.com/en/p2af0263/) commerce interfaces, you may want to manage URL parameters to synchronize them with the state of the interface. For example, when a user filters products on a search or [product listing page (PLP)](https://docs.coveo.com/en/m1sf3187/), you might want to update the URL to reflect the applied filters. 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. ## Create a hook on the `ParameterManager` controller Specify the `ParameterManager` controller in your [commerce engine configuration](https://docs.coveo.com/en/obif0156#define-the-commerce-engine-and-controllers). ```tsx // lib/commerce-engine-config.ts import { defineParameterManager, // ... } from '@coveo/headless-react/ssr-commerce'; export default { // ... controllers: { parameterManager: defineParameterManager(), // ... }, // ... }; ``` Retrieve the `ParameterManager` controller hook after defining your engine. ```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 { useParameterManager, // ... } = engineDefinition.controllers; ``` ## Create a component to manage URL parameters Create hooks to: * Deserialize URL parameters and update the state. * Serialize URL parameters from the state and update the URL. The Coveo Headless React package provides a [`buildParameterSerializer`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.buildParameterSerializer.html) function that you can use for that purpose, in addition to the `ParameterManager` controller. To avoid race conditions in Next.js, implement your parameter manager component as follows: ```tsx // components/ParameterManager.tsx 'use client'; import {useParameterManager} from '@/lib/commerce-engine'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; import {useSearchParams} from 'next/navigation'; import {useEffect, useMemo, useRef} from 'react'; export default function ParameterManager({url}: {url: string | null}) { const {state, methods} = useParameterManager(); const {serialize, deserialize} = buildParameterSerializer(); const initialUrl = useMemo(() => new URL(url ?? location.href), [url]); const previousUrl = useRef(initialUrl.href); const searchParams = useSearchParams(); const flag = useRef(true); <1> useEffect(() => { <2> if (methods === undefined) { <3> return; } if (flag.current) { flag.current = false; return; } const newCommerceParams = deserialize(searchParams); const newUrl = serialize(newCommerceParams, new URL(previousUrl.current)); if (newUrl === previousUrl.current) { return; } flag.current = true; <4> previousUrl.current = newUrl; methods.synchronize(newCommerceParams); }, [methods, serialize, deserialize, searchParams]); <5> useEffect(() => { <6> if (methods === undefined) { return; } if (flag.current) { flag.current = false; return; } const newUrl = serialize(state.parameters, new URL(previousUrl.current)); if (previousUrl.current === newUrl) { return; } flag.current = true; previousUrl.current = newUrl; history.pushState(null, document.title, newUrl); }, [methods, serialize, state.parameters]); <7> return null; } ``` <1> This [flag](#flag-explanation) serves to ensure that history navigation between pages doesn't clear commerce parameters and deserialized in history state loss. <2> When the URL search parameters change, this effect deserializes them and synchronizes them into the `ParameterManager` controller's state. <3> Ensures that the effect only executes if the controller is [hydrated](https://docs.coveo.com/en/p2ae0404/), so that it plays well with the other effect. We do the same in the other effect. <4> Set the [flag](#flag-explanation) to `true` to prevent the other effect from executing in case of parallel triggers such as the URL changing and the controller state changing. We do the same in the other effect. <5> Watch the required dependencies from the parameter manager and parameter serializer. Also watch the `searchParams` hook to trigger the deserialization effect when the URL search parameters change. <6> When the `ParameterManager` controller's state changes, this effect serializes it into the URL and pushes the new URL to the browser history. <7> Watch the required dependencies from the parameter manager and parameter serializer. Also watch the [`state.parameters`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.ParameterManagerState.html#parameters) to trigger the serialization effect when the controller state changes. **Custom serialization and deserialization of URL parameters**
Details If the default serialization and deserialization don't suit your needs, you can create your own functions. Use the following signatures: * For serialization: ```tsx (params: CommerceSearchParameters, urls: URL) => string ``` * For deserialization: ```tsx (params: URLSearchParams | Record) => CommerceSearchParameters ``` The `CommerceSearchParameters` object should implement the [`SearchParameters` interface](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Search.SearchParameters.html). For example, if you want to serialize the `q` parameter and facet values, the object may look like this: ```tsx { q: "gloves", f: { "category": [ "winter", ], "article": [ "A1-123", ], }, } ``` The serialization function takes that object and returns a URL with search parameters, such as `+https://example.com/?query=gloves&search_category=winter&search_article=A1-123+`. [example] .customSerialize
```tsx export function customSerialize(params: CommerceSearchParameters, url: URL): string { const searchParams = new URLSearchParams(); if (params.q) { <1> searchParams.set('query', params.q); } if (params.f) { <2> Object.entries(params.f).forEach(([facetName, facetValues]) => { if (facetValues && facetValues.length > 0) { const customKey = `search_${facetName}`; searchParams.set(customKey, facetValues[0]); <3> } }); } const newUrl = new URL(url); newUrl.search = searchParams.toString(); return newUrl.toString(); } ``` <1> Handles the `q` parameter so that it's serialized as `query`. <2> Handles the facets by prefixing them with the `search_` prefix and serializing them as `search_category=winter&search_article=A1-123`. <3> In this example, only the first value of each facet is used for simplicity, but you can modify this to handle multiple values as needed. #### The deserialization does the opposite: it takes a `URLSearchParams` object or a `Record` and returns a `CommerceSearchParameters` object. **customDeserialize** ```tsx export function customDeserialize( params: | URLSearchParams | Record ): CommerceSearchParameters { const searchParams = params instanceof URLSearchParams ? params : new URLSearchParams(params as any); const deserialized: CommerceSearchParameters = {}; const query = searchParams.get('query'); <1> if (query) { deserialized.q = query; } const facets: Record = {}; for (const [key, value] of searchParams.entries()) { <2> if (key.startsWith('search_') && value) { const facetName = key.replace('search_', ''); const decodedValue = decodeURIComponent(value).replace(//g, ' '); facets[facetName] = [decodedValue]; } } if (Object.keys(facets).length > 0) { deserialized.f = facets; } return deserialized; } ``` <1> Handles the `query` parameter to set the `q` property in the returned object. <2> Handles facets by looking for parameters prefixed with `search_` and decoding their values, storing them in the `f` property of the returned object. ##### === Flag explanation [Next.js caches](https://nextjs.org/docs/app/building-your-application/caching) the static state of a page the first time it renders it. Consequently, if we serialize the state parameters and push them to the browser history when navigating back to a page, any commerce parameters in the URL that weren't part of the controller's initial state will be lost. By having a guard that prevents effect execution when the flag is set to `true` and sets the flag back to `false`, we can prevent this. > **Note** > > This flag is necessary in a Next.js app, but it might not be necessary in other frameworks. > It's not necessary and would even break with [Remix](https://remix.run/), for example. > > It's also possible that this flag become unnecessary or problematic in future versions of Next.js. The logic involved here is quite complex and the exact [hydration](https://docs.coveo.com/en/p2ae0404/) sequence can vary between runs, so if you want to dive in, we recommend running the example project in the [Headless project repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/search/page.tsx) with breakpoints. But, for a high-level explanation, consider the following example sequence. . A user initially navigates to `/search?q=test`. Next.js caches this page's state with the `q=test` parameter, for future visits to that page. Next.js then hydrates the page. . Then, the user selects the next page of deserialized so that the URL becomes `/search?q=test&page=1`. Dynamically, Next.js updates the state, _but this new state isn't cached_ for future visits to that page. . Then, the user navigates to a product page (for example, `/product/123`). Next.js renders the page statically, caching the state, and then hydrates it. . At this point, if the user uses their browser history to go back to the search page, the URL will be `/search?q=test&page=1`, but the `ParameterManager` controller's state will have been reset to the cached state of that page (step 1), which only includes the `q=test` parameter. However, thanks to the flag, the navigation event won't cause the URL to be updated. Rather: .. The `flag` is initially set to `true`. Just after the static rendering of the search page: ... The `useSearchParams` hook, which runs first because it's defined first, tries to run, but is blocked by the flag. This also sets the flag to `false`. ... The second hook then runs, because the flag is set to `false`, but is stopped at the `previousUrl.current === newUrl` check, since in the cached static state, the `page=1` parameter isn't part of the controller's state. .. Then, as the page [hydrates](https://docs.coveo.com/en/p2ae0404/), the flag is still set to `false`, so: ... The `useSearchParams` hook, which runs first because it's defined first, causes the controller to synchronize its state with the URL, thus preserving the `page=1` parameter. This same hook also sets the flag to `true`. ... Since `state.parameters` has changed, the second hook triggers, attempting to update the URL, but doesn't run because the flag has just been set to `true`. ## Use your `ParameterManager` component in your search and listing pages Integrate your `ParameterManager` component in your search and listing pages to manage the URL parameters, as in the following example. ```tsx // app/search/page.tsx // ... import ParameterManager from '@/components/parameter-manager'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; import {searchEngineDefinition} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; export default async function Search({ searchParams, }: { searchParams: Promise; }) { const navigatorContext = new NextJsNavigatorContext(headers()); searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); // ... const {deserialize} = buildParameterSerializer(); const parameters = deserialize(await searchParams); <1> const staticState = await searchEngineDefinition.fetchStaticState({ controllers: { parameterManager: {initialState: {parameters}}, // ... }, }); return ( <2> ); } ``` <1> Deserialize the URL parameters and set them as initial values for to the `ParameterManager` component when fetching the static state. <2> Use the `ParameterManager` component to manage the URL parameters in your search page.