---
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.md) commerce interfaces, you may want to manage URL parameters to sync 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.md), 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.md#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.md), 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><summary>Details</summary>

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<string, SearchParamValue>) => 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`.

**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<string, SearchParamValue>` and returns a `CommerceSearchParameters` object.

**customDeserialize**

```tsx
export function customDeserialize(
  params:
    | URLSearchParams
    | Record<string, SearchParamValue>
): 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<string, string[]> = {};

  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.

</details>
### 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.md) 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.

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

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

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

1. 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.md), 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<URLSearchParams>;
}) {
  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 (
    <SearchProvider
      staticState={staticState}
      navigatorContext={navigatorContext.marshal}
    >
      <ParameterManager url={navigatorContext.location} /> <2>
      <!--- ... --->
    </SearchProvider>
  );
}
```
<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.