Managing client IDs with server-side rendering

This is for:

Developer

In server-side rendering (SSR), the initial request to Coveo APIs is made from the server rather than the client’s browser. This request requires a client ID to personalize responses based on the user’s behavior and previous interactions.

Without effective client ID management, you can’t accurately distinguish between new and returning visitors, leading to a lack of personalization and potential issues with session tracking.

There are two primary scenarios to consider when managing client IDs in an SSR context:

  • New visitors:

    When a user visits the site for the first time, the server generates a unique client ID. The client ID is used to send a request to Coveo, and the response is returned to the browser. This generated client ID is stored in the browser as a cookie, enabling the browser to send it to Coveo for future client-side requests.

  • Returning visitors:

    Users who have previously visited the site already have a client ID stored in their browser. When the server receives the request, it retrieves the client ID from the cookie and uses it to send a request to Coveo.

While the example in the following section uses Next.js, the same principles apply if you’re using a different language or framework in the back-end to perform SSR.

Note

If you’re using Coveo Relay to send client-side events and a client ID is stored in the browser, Relay will automatically use this stored client ID.

Code example with Next.js route handlers

The following example showcases how to call the search endpoint of the Commerce API server-side and return a pre-rendered HTML file with a list of products. The sample uses Next.js’s route handler mechanism.

Note

The example given here shows how to make a request to the Commerce API, however, similar logic applies if you’re using the Search API.

import { cookies } from "next/headers";
import { search } from "./search";

const COOKIE_NAME = "coveo_visitorId"; 1

interface Product {
  ec_name: string;
  ec_description: string;
  ec_price: number;
  ec_thumbnails: string[];
}

const generateHtmlResponse = (products: Product[]): string => `
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>SSR</title>
      <script>
        function deleteCookie() {
          document.cookie = '${COOKIE_NAME}=; Max-Age=0; path=/;';
          document.getElementById('cookieValue').innerText = 'No cookie set.';
          alert('Cookie deleted.');
        }
      </script>
    </head>
    <body>
      <h2>Product List</h2>
      ${
        products.length > 0
          ? `
        <ul>
          ${products
            .map(
              (product) => `
            <li>
              <h3>${product.ec_name}</h3>
              <p>${product.ec_description}</p>
              <p><strong>Price:</strong> ${product.ec_price}</p>
              <img src="${product.ec_thumbnails[0]}" alt="${product.ec_name}" style="width: 150px; height: 150px; object-fit: cover;" />
            </li>
          `
            )
            .join("")}
        </ul>
      `
          : `<p>No products found.</p>`
      }
    </body>
  </html>
`;

const getOrCreateClientId = () => { 2
  const cookieStore = cookies();
  const existingClientId = cookieStore.get(COOKIE_NAME);
  return existingClientId ? existingClientId.value : crypto.randomUUID();
};

export const GET = async (request: Request) => {
  const clientId = getOrCreateClientId(); 3

  const query = "kayak";
  const userAgent = request.headers.get("user-agent") || "";
  const locationUrl = request.url;
  const referrer = request.headers.get("referer") || "";

  const searchResults = await search( 4
    query,
    clientId,
    userAgent,
    locationUrl,
    referrer
  );

  const products: Product[] = searchResults.success 5
    ? searchResults.data.products || []
    : [];

  const htmlResponse = generateHtmlResponse(products);

  return new Response(htmlResponse, { 6
    status: 200,
    headers: {
      "Content-Type": "text/html",
      "Set-Cookie": `${COOKIE_NAME}=${clientId}; Path=/;`,
    },
  });
};
1 Define the name of the cookie that stores the Coveo client ID.
2 Create a function that checks if there’s an existing client ID present in cookies(). If a client ID is found, return it; otherwise, generate and return a new client ID.
3 Get or create a client ID by using the getOrCreateClientId function defined earlier.
4 Call the search helper function to make a request to the Commerce API. Implementation details for this function are below.
5 Retrieve a list of products returned from the search call.
6 Return the htmlResponse generated by the generateHtmlResponse helper function. Set the client ID cookie by using the Set-Cookie header.

The search function that makes a request to the Commerce API is as follows:

export async function search(
  query: string,
  clientId: string,
  userAgent: string,
  locationUrl: string,
  referrer: string
) {

  const requestBody = { 1
    trackingId:  "<TRACKING_ID>",
    clientId,
    context: {
      view: {
        url: locationUrl,
      },
      capture: true,
      cart: [],
      user: {
        userAgent,
        referrer,
      },
    },
    language: "en",
    country: "US",
    currency: "USD",
    page: 0,
    perPage: 5,
    facets: [],
    sort: { sortCriteria: "relevance" },
    query,
  };

  const headers = { 2
    "Content-Type": "application/json",
    Authorization: `Bearer "<TOKEN>"`,
    "User-Agent": userAgent,
  };

  try {
    const response = await fetch("<URL>", { 3
      method: "POST",
      headers,
      body: JSON.stringify(requestBody),
    });

    if (!response.ok) {
      const errorMessage = await response.text();
      return {
        success: false,
        status: response.status,
        message: errorMessage || "Failed to fetch data from Coveo",
      };
    }

    return {
      success: true,
      data: await response.json(),
    };
  } catch (error) {
    return {
      success: false,
      status: 500,
      message: "Failed to fetch data from Coveo",
    };
  }
}
1 Define the request body to send to the Commerce API. Specify the tracking ID and other parameters, such as language and currency. Make sure to forward context.view.url, userAgent, and referrer from the original request to Coveo.
2 Define the headers to send with the request, including a token to authenticate the request.
3 Make a POST request to the Commerce API with the request body and headers. The URL of the request depends on your organization ID. For more details, see Organization endpoints.