--- title: Core concepts (Shopify Hydrogen) slug: p3ae0283 canonical_url: https://docs.coveo.com/en/p3ae0283/ collection: coveo-for-commerce source_format: adoc --- # Core concepts (Shopify Hydrogen) ## What is Coveo Headless? The Coveo Headless library is a [Redux-based](https://redux.js.org/) toolset for developing UI components. It provides utilities for interacting with the [Coveo Platform](https://docs.coveo.com/en/186/) and building user interfaces for various [product discovery solutions](https://docs.coveo.com/en/o9cf0524/). The [`@coveo/headless-react`](https://www.npmjs.com/package/@coveo/headless-react) package provides utilities for React that are compatible with a Hydrogen storefront. You should use the Headless library instead of working directly with the REST APIs. Headless abstracts the complexity of Coveo's APIs and provides a developer-friendly interface. Under the hood, Headless uses the [Commerce API](https://docs.coveo.com/en/103/) to query products from Coveo and the [Event Protocol](https://docs.coveo.com/en/o9je0592/) to send [Coveo Analytics events](https://docs.coveo.com/en/260/). ## Server-side rendering Server-side rendering (SSR) is a technique that allows web applications to render on the server before sending them to the client. This approach is particularly useful when developing with the Coveo Headless 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. At a high level, the process looks like the following: ![SSR diagram | Coveo for Commerce](https://docs.coveo.com/en/assets/images/headless/ssr-commerce.svg) ## Headless 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/). The Headless commerce engine is defined as a singleton, meaning a single instance is shared between the server and client. A Headless _controller_ is an abstraction that simplifies the implementation of a specific Coveo-powered UI feature or component. Controllers provide programming interfaces for interacting with the Headless engine's state. **Example** To implement the cart component, use the [`Cart`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.Cart.html) controller, available in the `@coveo/headless-react/ssr-commerce` package. This controller exposes public methods such as `purchase` and `empty`. Write code to call the appropriate controller methods when a user completes a purchase or empties the cart. These methods update the cart state in the commerce engine and trigger the necessary API calls to the Coveo Platform. When defining your engine, specify the controllers to use in your commerce site. On the client side, use controller hooks to interact with the engine in your components. ### Initializing the engine Initialize the Headless engine by first defining its configuration and controllers. ```ts // app/lib/commerce-engine-config.ts import { <1> CommerceEngineDefinitionOptions, defineContext, defineSummary, defineRecommendations, defineStandaloneSearchBox, } from '@coveo/headless-react/ssr-commerce'; export default { configuration: { organizationId: 'barcagroupproductionkwvdy6lp', <2> accessToken: 'xx697404a7-6cfd-48c6-93d1-30d73d17e07a', <3> analytics: { <4> enabled: true, trackingId: 'shop_en_us', }, context: { <5> language: 'en', country: 'US', currency: 'USD', view: { url: 'https://shop.barca.group', }, }, }, controllers: { <6> context: defineContext(), summary: defineSummary(), standaloneSearchBox: defineStandaloneSearchBox({ options: {redirectionUrl: '/search', id: 'standalone-search-box'}, }), homepageRecommendations: defineRecommendations({ options: {slotId: '9a75d3ba-c053-40bf-b881-6d2d3f8472db'}, }), cartRecommendations: defineRecommendations({ options: {slotId: '5a93e231-3b58-4dd2-a00b-667e4fd62c55'}, }), pdpRecommendationsLowerCarousel: defineRecommendations({ options: {slotId: 'a24b0e9c-a8d2-4d4f-be76-5962160504e2'}, }), pdpRecommendationsUpperCarousel: defineRecommendations({ options: {slotId: '05848244-5c01-4846-b280-ff63f5530733'}, }), // ... }, } satisfies CommerceEngineDefinitionOptions; ``` <1> Import the necessary controller definers for the commerce engine. <2> The `organizationId` 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). See [Authenticate requests](https://docs.coveo.com/en/p25e0439/). <4> 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/). <5> 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 current URL. <6> Specify the controllers to define as part of the commerce engine definition. Next, use your engine configuration to define the commerce engine, as in the following example: ```ts // app/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, searchEngineDefinition, recommendationEngineDefinition, standaloneEngineDefinition, useEngine, } = engineDefinition; <2> export const { useContext, useSummary, useStandaloneSearchBox, useHomepageRecommendations, useCartRecommendations, usePdpRecommendationsLowerCarousel, usePdpRecommendationsUpperCarousel, } = engineDefinition.controllers; <3> ``` <1> Define the commerce engine using the target commerce engine configuration. <2> Engines for different use cases in your commerce site. These engine definitions will be imported in your components to access the engine state. <3> Retrieve the controller hooks created based on the controllers definitions you provided in the commerce engine configuration. ## What is static state and how is it used by the client? Static state refers to a precomputed snapshot of the Coveo Headless engine's state, fetched on the server before sending the page to the client. This ensures that when the client receives the page, it already contains the necessary search parameters, filters, cart state, and navigation data without requiring an additional request. Static state is essential for [SSR](https://docs.coveo.com/en/p2af0263/) because it enables fast page loads while ensuring that search-related UI components are correctly populated. Additionally, [hydration](https://docs.coveo.com/en/p2ae0404/) is the process of reusing the static state on the client side. This allows the search engine to seamlessly continue from where the server left off without needing to fetch data again. ## Using providers Providers establish the context and manage state for a Coveo Headless engine within a Hydrogen application. They ensure that the engine has the necessary navigation data and [static state](#what-is-static-state-and-how-is-it-used-by-the-client) for [server-side rendering (SSR)](https://docs.coveo.com/en/p2af0263/) while enabling client-side [hydration](https://docs.coveo.com/en/p2ae0404/) for dynamic updates. To enable SSR with Coveo Headless and Hydrogen, you must create two types of providers: * [Navigation context providers](#navigation-context-providers) * [Headless-specific providers](#headless-specific-providers) ### Navigation context providers Navigation context providers wrap relevant navigation context information and make it available to the Headless engine. They collect key information about the user's environment, such as: * **Referrer**: The previous page's URL (if available). * **User agent**: Information about the user's browser or device. * **Location**: The current URL being accessed. * [.initial]**[client ID](https://docs.coveo.com/en/lbjf0131/)**: A unique identifier for the user's session or visit. Depending on whether navigation context is fetched on the client or server, you must create two different providers. #### Server-side and client navigation context providers The following example shows how to create a server-side and client-side navigation context provider: ```ts // app/lib/navigator-provider.ts import type {NavigatorContext} from '@coveo/headless/ssr-commerce'; import {getCookieFromRequest, getCookie} from './session'; export class ServerSideNavigatorContextProvider implements NavigatorContext { <1> private request: Request; private generatedId?: string; constructor(request: Request) { this.request = request; } get referrer() { return this.request.referrer; } get userAgent() { return this.request.headers.get('user-agent'); } get location() { return this.request.url; } get clientId() { <2> const idFromRequest = getCookieFromRequest(this.request, 'coveo_visitorId'); if (idFromRequest) { return idFromRequest; } if (this.generatedId) { return this.generatedId; } const generated = crypto.randomUUID(); this.generatedId = generated; return generated; } get marshal(): NavigatorContext { return { clientId: this.clientId, location: this.location, referrer: this.referrer, userAgent: this.userAgent, }; } } export class ClientSideNavigatorContextProvider implements NavigatorContext { get referrer() { return document.referrer; } get userAgent() { return navigator.userAgent; } get location() { return window.location.href; } get clientId() { <3> return getCookie(document.cookie, 'coveo_visitorId') || 'MISSING'; } get marshal(): NavigatorContext { return { clientId: this.clientId, location: this.location, referrer: this.referrer, userAgent: this.userAgent, }; } } ``` <1> The [`NavigatorContext`](https://docs.coveo.com/en/headless-react/latest/reference/interfaces/SSR_Commerce.index.NavigatorContext.html) interface defines the required properties for the server navigation context provider. The same interface is used for the client navigation context provider later. <2> The `clientId` getter retrieves the unique visitor ID from a cookie if available; otherwise, it generates and stores a new one. If the user is a returning visitor, it fetches the visitor ID from the `coveo_visitorId` cookie using [`getCookieFromRequest()`](#helper-cookie-methods) helper. If the cookie doesn't exist, it checks whether an ID has already been generated in this session. If no ID exists, it generates a new UUID and stores it in `this.generatedId` for future calls. See [Managing client IDs with server-side rendering](https://docs.coveo.com/en/oa890580/) for more information. <3> The server has already set the `coveo_visitorId` cookie, so the client can retrieve it using the [`getCookie()`](#helper-cookie-methods) helper. ##### Helper cookie methods Define helper methods for working with cookies as illustrated in the following example: ```ts // app/lib/session.ts // ... export function getCookie(cookieString: string, name: string) { <1> const cookieName = `${name}=`; const cookies = cookieString.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.indexOf(cookieName) === 0) { return cookie.substring(cookieName.length, cookie.length); } } return null; } export function getCookieFromRequest(request: Request, name: string) { <2> return getCookie(request.headers.get('Cookie') || '', name); } ``` <1> The `getCookie` method extracts the value of a specific cookie by its name from a given `cookieString`. <2> The `getCookieFromRequest` method extracts a cookie value from an HTTP request by using the `getCookie` function. ### Headless-specific providers Once you have the navigation context providers, you can create Headless-specific providers. Any components that use Headless hooks must be wrapped within a Headless provider. Coveo Headless React provides the [`buildProviderWithDefinition`](https://docs.coveo.com/en/headless-react/latest/reference/functions/SSR_Commerce.index.buildProviderWithDefinition.html) utility function to create a provider for a given engine definition. These providers will be used to wrap your components and provide the necessary context and state to the Headless engine. ```ts // app/lib/providers.tsx import {buildProviderWithDefinition} from '@coveo/headless-react/ssr-commerce'; import { searchEngineDefinition, standaloneEngineDefinition, recommendationEngineDefinition, } from './commerce-engine'; export const SearchProvider = buildProviderWithDefinition( searchEngineDefinition, ); export const StandaloneProvider = buildProviderWithDefinition( standaloneEngineDefinition, ); export const RecommendationProvider = buildProviderWithDefinition( recommendationEngineDefinition, ); ``` To use the Headless provider, wrap your components with the provider and provide two required props: * `navigatorContext`: Provide the navigator. Since `navigatorContext` providers [have already been created](#server-side-and-client-navigation-context-providers), you can use them to provide the server navigator context to the Headless provider. * `staticState`: Use the pre-fetched state from the server to ensure that the engine has an initial state before rendering. To get the static state, define a [helper method](#fetch-static-headless-state) to fetch the static Headless state. To understand how this is concretely used when displaying components, see [Displaying the search page with the search provider](https://docs.coveo.com/en/p27e1343#displaying-the-search-page-with-the-search-provider). #### Fetch static Headless state Define the `fetchStaticState` helper method to fetch the static Headless state. As this method will be invoked by your route's [`loader`](https://remix.run/docs/en/main/route/loader) function, it will run on the server to retrieve the static state. The `loader` function can then return the static state to the client for [hydration](https://docs.coveo.com/en/p2ae0404/). ```ts // app/lib/commerce-engine.ts import type {AppLoadContext} from '@shopify/remix-oxygen'; import { type InferStaticState } from '@coveo/headless-react/ssr-commerce'; // . . . function getLocale() { const country = 'US'; const language = 'en'; const currency = 'USD'; return {country, language, currency}; } export async function fetchStaticState({ k, query, url, context, request, }: { k: | 'listingEngineDefinition' | 'searchEngineDefinition' | 'standaloneEngineDefinition'; query: string; url: string; context: AppLoadContext; request: Request; }) { const {country, language, currency} = getLocale(request); <1> const cart = await context.cart.get(); return engineDefinition[k].fetchStaticState({ <2> controllers: { parameterManager: {initialState: {parameters: {q: query}}}, <3> cart: { initialState: mapShopifyCartToCoveoCart(cart), <4> }, context: { <5> language: language.toLowerCase(), country, currency: currency as any, view: { url, }, }, }, }); } export type SearchStaticState = InferStaticState; <5> ``` <1> Extract the [locale](https://docs.coveo.com/en/p4tf0351/) information from the request using the custom `getLocale` helper method. Here, the [locale](https://docs.coveo.com/en/p4tf0351/) information is hardcoded, but you can extract it from the request. <2> The [`fetchStaticState`](https://docs.coveo.com/en/headless-react/latest/reference/types/SSR_Commerce.index.FetchStaticState.html#tcontrollersprops) method on the Headless engine fetches the static state for the given engine definition, requiring the controllers and their initial states as arguments. <3> The `parameterManager` controller initializes the search query parameter. For more details on managing parameters, see [Managing parameters](https://docs.coveo.com/en/p23d7042/). <4> The `cart` controller initializes the cart state using the `mapShopifyCartToCoveoCart` helper method. For more details on converting the Shopify cart to a Headless cart, see [Managing the cart](https://docs.coveo.com/en/p1oe0470#convert-the-shopify-cart-to-a-headless-cart) documentation. <5> The `context` controller initializes the context state with the user's [locale](https://docs.coveo.com/en/p4tf0351/) information and the current URL. Additionally, define a `fetchRecommendationStaticState` helper method specifically for recommendations. ```ts // app/lib/commerce-engine.ts // . . . export async function fetchRecommendationStaticState({ k, context, request, productId, }: { context: AppLoadContext; request: Request; k: ( <1> | 'homepageRecommendations' | 'cartRecommendations' | 'pdpRecommendationsLowerCarousel' | 'pdpRecommendationsUpperCarousel' )[]; productId?: string; <2> }) { const cart = await context.cart.get(); const {country, language, currency} = getLocaleFromRequest(request); return engineDefinition.recommendationEngineDefinition.fetchStaticState({ controllers: { homepageRecommendations: { enabled: k.includes('homepageRecommendations'), <3> productId, }, cartRecommendations: { enabled: k.includes('cartRecommendations'), productId, }, pdpRecommendationsLowerCarousel: { enabled: k.includes('pdpRecommendationsLowerCarousel'), productId, }, pdpRecommendationsUpperCarousel: { enabled: k.includes('pdpRecommendationsUpperCarousel'), productId, }, cart: { initialState: mapShopifyCartToCoveoCart(cart), <4> }, context: { language: language.toLowerCase(), country, currency: currency as any, view: { url: 'https://shop.barca.group', }, }, }, }); } ``` <1> The values for `k` are the different types of recommendations that can be fetched. <2> Optional `productId` parameter to fetch recommendations based on a specific product as seed. <3> Set the `enabled` property to `true` if the recommendation type is included in the `k` array, and `false` otherwise. This optimization lets you fetch and refresh only the target recommendations. <4> The `cart` controller initializes the cart state using the `mapShopifyCartToCoveoCart` helper method. For more details on converting the Shopify cart to a Headless cart, see [Managing the cart](https://docs.coveo.com/en/p1oe0470#convert-the-shopify-cart-to-a-headless-cart) documentation.