--- title: Headless commerce usage (server-side rendering) slug: obif0156 canonical_url: https://docs.coveo.com/en/obif0156/ collection: coveo-for-commerce source_format: adoc --- # Headless commerce usage (server-side rendering) > **Important** > > The Headless Commerce SSR utilities are in open beta. > Contact your Coveo representative for support in adopting this. [server-side rendering (SSR)](https://docs.coveo.com/en/p2af0263/) is a technique that allows web applications to be rendered on the server before sending them to the client. This approach is particularly useful when developing with the [Coveo Headless](https://docs.coveo.com/en/lcdf0493/) framework because it enables faster initial loading times and better SEO. By rendering the HTML on the server, the client can receive a fully formed page which is ready to be displayed as soon as it's received. The [`@coveo/headless-react`](https://www.npmjs.com/package/@coveo/headless-react) package includes utilities for [React](https://react.dev/) which are compatible with [Next.js](https://nextjs.org/) in the `@coveo/headless-react/ssr-commerce` sub-package. These utilities enable SSR with the [Headless](https://docs.coveo.com/en/lcdf0493/) framework. This article introduces the different pieces required to put together a Headless SSR commerce app using a Next.js example. It assumes you already understand how to [set up a Next.js project](https://nextjs.org/docs/app/getting-started/installation). > **Note** > > While the core concepts discussed in this article apply to SSR with React, the code samples are specific to Next.js. > > Depending on your use case, you might find other relevant samples in the [project repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr). At a high level, here's the architecture involved: ![SSR diagram](https://docs.coveo.com/en/assets/images/headless/ssr-commerce.svg) > **Tip** > > To focus on relevant concepts and ease comprehension, the code samples in this article are simplified. > For a complete example, see the [headless-ssr-commerce-nextjs](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs) sample. > Links to different parts of the sample will be provided throughout this article. ## Install the library Use npm to install [Headless React Utils for SSR](https://www.npmjs.com/package/@coveo/headless-react): ```bash npm install @coveo/headless-react ``` ## Define the commerce engine and controllers The Headless commerce _engine_ manages the state of the [product discovery solution](https://docs.coveo.com/en/o9cf0524/) interface and communicates with the [Coveo Platform](https://docs.coveo.com/en/186/). Rather than _initializing_ the commerce engine in the client, you must _define_ a commerce engine. This is because the commerce engine definition is a singleton that must be shared between the server and the client. A Headless _controller_ is an abstraction that simplifies the implementation of a specific Coveo-powered UI feature or component. In other words, controllers provide programming interfaces for interacting with the Headless engine's state. When defining your engine, specify the controllers you want to use in your commerce site. You'll then be able to retrieve controller hooks to interact with the engine in your components. The following is an example commerce engine configuration file. ```tsx // lib/commerce-engine-config.ts import {<1> CommerceEngineDefinitionOptions, defineContext, defineSummary, defineRecommendations, // ... } from '@coveo/headless-react/ssr-commerce'; export default { configuration: { organizationId: 'searchuisamples', <2> accessToken: 'xx564559b1-0045-48e1-953c-3addd1ee4457', <3> context: { <4> language: 'en', country: 'US', currency: 'USD', view: { url: 'https://sports.barca.group', }, }, analytics: { <5> trackingId: 'sports-ui-samples', }, cart: { <6> items: [ { // ... }, ], }, }, controllers: { <7> context: defineContext(), summary: defineSummary(), popularViewed: defineRecommendations({ <8> options: { slotId: 'd73afbd2-8521-4ee6-a9b8-31f064721e73', }, }), // ... }, } satisfies CommerceEngineDefinitionOptions; ``` <1> Import the necessary controller definers. <2> The organization ID is the [unique identifier of your Coveo organization](https://docs.coveo.com/en/n1ce5273/). <3> The access token is a [search token](https://docs.coveo.com/en/o8ld0051#filtering-content-using-search-tokens) or an [API key](https://docs.coveo.com/en/o8ld0051#api-keys) that grants the **Allowed** [access level](https://docs.coveo.com/en/2818/) on the [**Execute Queries**](https://docs.coveo.com/en/1707#execute-queries-domain) [domain](https://docs.coveo.com/en/2819/) and the **Push** [access level](https://docs.coveo.com/en/2818/) on the [**Analytics Data**](https://docs.coveo.com/en/1707#administrate-domain) [domain](https://docs.coveo.com/en/2819/) in the target [organization](https://docs.coveo.com/en/185/). To improve security, client-side specification of user IDs isn't supported by Event Protocol. To specify user IDs, enforce them through [search tokens](https://docs.coveo.com/en/56/). <4> The [`context`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.CommerceEngineConfiguration.html#context) object contains information about the user's context, such as the currency, country, language, and the URL. Every time the user navigates between pages, you must update this context. <5> Via the [`analytics`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.CommerceEngineConfiguration.html#analytics) object, specify the [tracking ID](https://docs.coveo.com/en/o8rb0139/). <6> Pass in the initial state of the cart by specifying the [`CartInitialState`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.CommerceEngineConfiguration.html#cart) object. <7> Specify the controllers to define as part of the commerce engine definition. <8> Certain controller definers require additional options. For example, here you must specify which recommendation slot to use. Use your engine configuration to define the commerce engine, as in the following example. ```tsx // lib/commerce-engine.ts import {defineCommerceEngine} from '@coveo/headless-react/ssr-commerce'; import engineConfig from './commerce-engine-config'; export const engineDefinition = defineCommerceEngine(engineConfig); <1> export const { listingEngineDefinition, <2> searchEngineDefinition, recommendationEngineDefinition, standaloneEngineDefinition, useEngine, <3> } = engineDefinition; export const { useContext, useSummary, usePopularViewed, // ... } = engineDefinition.controllers; <4> ``` <1> Define the commerce engine using the target commerce engine configuration. <2> Engines for different use cases in your commerce site. More on that later. <3> `useEngine` is a hook that lets you access the core commerce engine. More on that later. <4> Retrieve the controller hooks created based on the controllers definitions you provided in the commerce engine configuration. For complete code samples, see [commerce-engine-config.ts](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/lib/commerce-engine-config.ts) and [commerce-engine.ts](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/lib/commerce-engine.ts). ## Create a navigation context provider For SSR, you must create a navigation context provider that will wrap relevant navigation context information (such as the URL and [`clientId`](https://docs.coveo.com/en/masb0234/)) and make it available to the server. ### Capture the navigation context with middleware Create a [Next.js middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) to handle the URL and client ID. For simplicity, this example always generates a new [`clientId`](https://docs.coveo.com/en/masb0234/) for each request. In a real implementation, you would likely use a more sophisticated approach to generate and manage client IDs with browser cookies or local storage. See [Managing client IDs with server-side rendering](https://docs.coveo.com/en/oa890580/). ```tsx // middleware.ts <1> import {NextRequest, NextResponse} from 'next/server'; export default function middleware(request: NextRequest) { const response = NextResponse.next(); const requestHeaders = new Headers(request.headers); const uuid = crypto.randomUUID(); <2> requestHeaders.set('x-coveo-client-id', uuid); <3> response.headers.set('x-coveo-client-id', uuid); response.headers.set('x-href', request.nextUrl.href); <4> return response; } ``` <1> [Next.js](https://nextjs.org/docs/app/building-your-application/routing/middleware) treats `middleware.ts` as a middleware file. <2> Generate a unique `clientId` for the request and response. <3> Set the `clientId` in both the request and response headers so that it can be used by the commerce engine server-side and client-side across sessions. <4> Make sure to pass along the `x-href` header, which is the URL of the request, in the response. More on that below. ### Create the navigation context object Then, in your Next.js app, create a navigation context provider that will use the headers from the middleware to create a navigation context object. ```tsx // lib/navigatorContextProvider.ts import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import type {ReadonlyHeaders} from 'next/dist/server/web/spec-extension/adapters/headers'; export class NextJsNavigatorContext implements NavigatorContext { constructor(private headers: ReadonlyHeaders) {} <1> get referrer() { return this.headers.get('referer') || this.headers.get('referrer'); <2> } get userAgent() { return this.headers.get('user-agent'); } get location() { return this.headers.get('x-href'); } get clientId() { const clientId = this.headers.get('x-coveo-client-id'); return clientId!; } get marshal(): NavigatorContext { <3> return { clientId: this.clientId, location: this.location, referrer: this.referrer, userAgent: this.userAgent, }; } } ``` <1> Takes in the read-only [headers](https://nextjs.org/docs/app/api-reference/functions/headers) from a Next.js request, which let you access request-specific data. <2> Some browsers use `referer` while others may use `referrer`. <3> Marshals the navigation context into a format that can be used by Coveo's Headless library. See [`NavigatorContext`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.NavigatorContext.html) for more information. You'll use your context provider later in the different [pages](#assemble-pages) of your site (more details on creating pages later). See [navigatorContextProvider.ts](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/lib/navigatorContextProvider.ts) and [page.tsx](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/search/page.tsx) for complete code samples. ## Create providers Providers take care of displaying your page with the static state, and then [hydrating](https://docs.coveo.com/en/p2ae0404/) the state and displaying the page with the hydrated state. They're required for your controller hooks to function. ![SSR diagram](https://docs.coveo.com/en/assets/images/headless/ssr-commerce.svg) Coveo Headless React exposes the https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.buildProviderWithDefinition.html[`buildProviderWithDefinition`^] utility function that creates a provider for a given engine definition. It simplifies the process of building providers for your components. ```tsx // components/providers/providers.tsx 'use client'; import { listingEngineDefinition, recommendationEngineDefinition, searchEngineDefinition, standaloneEngineDefinition, } from '@/lib/commerce-engine'; import {buildProviderWithDefinition} from '@coveo/headless-react/ssr-commerce'; export const ListingProvider = buildProviderWithDefinition( <1> listingEngineDefinition ); export const SearchProvider = buildProviderWithDefinition( <2> searchEngineDefinition ); export const RecommendationProvider = buildProviderWithDefinition( <3> recommendationEngineDefinition ); export const StandaloneProvider = buildProviderWithDefinition( <4> standaloneEngineDefinition ); ``` <1> For listing pages to provide context for listing-specific hooks. <2> For search pages to provide context for search-specific hooks. <3> For recommendation slots, regardless of the slot location (for example, home page, product detail page, or some other page.) <4> For components that don't require triggering a search or product fetch (such as a cart page or standalone search box). ## Create components In a Headless SSR commerce implementation, contrary to a non-SSR one, you generally don't interact with the controllers or engine directly in your components. We rather recommend leveraging the controller and engine hooks retrieved [earlier](#define-the-commerce-engine-and-controllers). For example, here's how to implement a `ShowMore` component. ```tsx // components/show-more.tsx 'use client'; <1> import {usePagination, useSummary} from '@/lib/commerce-engine'; <2> export default function ShowMore() { const pagination = usePagination(); const summary = useSummary(); const isDisabled = () => { <3> return ( !pagination.methods || summary.state?.lastProduct === summary.state?.totalNumberOfProducts ); }; return ( <>
Displaying {summary.state?.lastProduct ?? pagination.state.pageSize} out of{' '} {pagination.state.totalEntries} products
); } ``` <1> Runs client-side. <2> Import the relevant controller hooks from the commerce engine. <3> Use the hooks to access the state and methods of the controllers. For more samples, see [components](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/components/). ### Create a component that lets users change context In a Coveo commerce SSR setup, you might want to create a component that lets users change the context of the engine. For example, you might need to let users change the language, country and currency displayed on your site. You can do so with the [`useEngine`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.defineCommerceEngine.html) hook and a hook on the [`Context`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.Context.html) controller. ```tsx // components/context-dropdown.tsx 'use client'; import {useContext, useEngine} from '@/lib/commerce-engine'; import { CommerceEngine, ContextOptions, loadProductListingActions, loadSearchActions, } from '@coveo/headless-react/ssr-commerce'; const storefrontAssociations = [ <1> 'en-CA-CAD', 'fr-CA-CAD', 'en-GB-GBP', 'en-US-USD', ]; export default function ContextDropdown({ useCase, }: { useCase?: 'listing' | 'search'; }) { const context = useContext(); const engine = useEngine(); return (

Context dropdown :

); } ``` <1> A hardcoded list of storefront associations for switching app context by language, country, and currency, for demonstration purposes. In a real application, these values would likely come from sources like environment variables or an API. <2> Use the `context` controller hook to access the context state. <3> Similarly, use the `context` controller hook to access the context methods. <4> Check the `useCase` prop to determine whether the component is being used for search or listing. <5> After adjusting the content, dispatch an action that updates the content by triggering a query. For a complete sample, see [context-dropdown.tsx](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/components/context-dropdown.tsx). ## Assemble pages Once you've created your providers and the components you want to include in your site, assemble your pages using them. ### Select the appropriate engine and provider When doing so, use the target engine definition and the appropriate provider to wrap your components: |=== | Use case | Engine definition | Provider | Search | `searchEngineDefinition` | `SearchProvider` | Listing | `listingEngineDefinition` | `ListingProvider` | Recommendations | `recommendationEngineDefinition` | `RecommendationProvider` | Standalone search box | `standaloneEngineDefinition` | `StandaloneProvider` | Cart page | `standaloneEngineDefinition` | `StandaloneProvider` |=== If you use an incompatible engine definition or provider for a given use case, you may encounter errors, missing properties, or unexpected behavior. ### Example The following is a simplified search page example, integrating the different pieces you've created. For more complete instructions, see [Build search interfaces (SSR)](https://docs.coveo.com/en/p25b0411/). ```tsx // app/search/page.tsx // ... import ShowMore from '@/components/show-more'; import {SearchProvider} from '@/components/providers/providers'; import {searchEngineDefinition} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {headers} from 'next/headers'; export default async function Search() { const navigatorContext = new NextJsNavigatorContext(headers()); <1> searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); const items = { // ... <2> }; const staticState = await searchEngineDefinition.fetchStaticState({ <3> controllers: { cart: {initialState: items}, context: { language: 'en', country: 'US', currency: 'USD', view: { url: 'https://sports.barca.group/search', }, }, }, }); return ( staticState={staticState} <5> navigatorContext={navigatorContext.marshal} > <6> ); } export const dynamic = 'force-dynamic'; <7> ``` <1> As discussed [earlier](#create-a-navigation-context-provider), use your navigation context provider to set the navigator context before fetching the app static state. Below, you'll pass the navigator context to the `SearchProvider` component in the `return` value. <2> Retrieve cart items, because you'll need them when fetching the static state. The details are omitted here for brevity. In a real implementation, you would use your own logic to fetch the cart items, depending on your commerce site setup. <3> Fetches the static state of the app with initial state. Note that you must pass initial values for the `cart` and `context` controllers, so the static state can be generated with the target initial state. <4> Wrap your search page components with the `SearchProvider` component. <5> Pass the static state and navigator context to the `SearchProvider` component, which wraps all components in your page. <6> Include the target components. <7> Force the server to always dynamically generate the page, based on fresh context. ## What's next? For more samples, see the [UI-KIT repository](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app). See especially the following, which exemplify the essential components of a Coveo Commerce SSR implementation: * [Search page](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/search/page.tsx) * [Product listing page with standalone search box](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/(listing)/%5Bcategory%5D/page.tsx) * [Cart page with recommendations and standalone search box](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/cart/page.tsx) * [Product listing page with standalone searchbox](https://github.com/coveo/ui-kit/tree/main/samples/headless-ssr/commerce-nextjs/app/products/%5BproductId%5D/page.tsx)