---
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.