--- title: Getting started (Shopify Hydrogen) slug: p1oe0067 canonical_url: https://docs.coveo.com/en/p1oe0067/ collection: coveo-for-commerce source_format: adoc --- # Getting started (Shopify Hydrogen) This article introduces key concepts for using the [Coveo Headless library](https://docs.coveo.com/en/lcdf0493/). It includes code samples to help you set up a standalone search box where you can enter queries and receive suggestions. Some functionalities have been omitted to keep the example short. For more detailed information, see the related links in each section. As a prerequisite, you must have a Shopify Hydrogen storefront set up and running. This can be done by following the [Shopify Hydrogen documentation](https://shopify.dev/docs/storefronts/headless/hydrogen/getting-started). ## Install Headless Use npm to install the [Headless React](https://www.npmjs.com/package/@coveo/headless-react) package. ```bash npm install @coveo/headless-react ``` ## Define the commerce engine and controllers Create an engine configuration file to specify how Headless communicates with Coveo. Define the commerce engine configuration details and specify the controllers to use in your commerce interface. ```ts // app/lib/commerce-engine-config.ts import { CommerceEngineDefinitionOptions, defineContext, defineSummary, defineRecommendations, defineStandaloneSearchBox, } from '@coveo/headless-react/ssr-commerce'; export default { configuration: { accessToken: 'xx697404a7-6cfd-48c6-93d1-30d73d17e07a', organizationId: 'barcagroupproductionkwvdy6lp', analytics: { enabled: true, trackingId: 'shop_en_us', }, context: { language: 'en', country: 'US', currency: 'USD', view: { url: 'https://shop.barca.group', }, }, }, controllers: { 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; ``` 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. For more details on how engines and controllers work, see [Core concepts: Headless engine and controllers](https://docs.coveo.com/en/p3ae0283#headless-engine-and-controllers). ## Using providers Providers establish the context and manage state for a Coveo Headless engine within a Hydrogen application. To enable server-side rendering (SSR) with Coveo Headless and Hydrogen, you must create two types of providers: * Navigation context providers * Headless-specific providers For more details on how providers are used, see [Core concepts: Using providers](https://docs.coveo.com/en/p3ae0283#using-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, ); ``` #### Fetch static Headless state Let's define a helper function to fetch the static state of the Headless engine. More on how it's used later. 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 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, url, }: { k: | 'listingEngineDefinition' | 'searchEngineDefinition' | 'standaloneEngineDefinition'; url: string; }) { const {country, language, currency} = getLocale(); <1> return engineDefinition[k].fetchStaticState({ <2> controllers: { context: { language: language.toLowerCase(), country, currency: currency as any, view: { url, }, }, }, }); } export type StandaloneStaticState = InferStaticState; ``` <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 fetches the static state for the given engine definition using the provided controllers as the basis for initial state configuration. Here, we specify only the initial context state. In a real application, you would also specify the initial state for other controllers, such as the `parameterManager` and `cart` controllers. For more details see, [Core concepts: Fetch static Headless state](https://docs.coveo.com/en/p3ae0283#fetch-static-headless-state). ## Build the search box component Now that you have the engine and providers set up, you can build a search box component using the Headless library. The following example shows how to build a search box component using the `useStandaloneSearchBox` hook. The component features a search input field, a submit button, and a dynamic list of query suggestions. ```tsx // app/components/StandaloneSearchBox.tsx import {useEffect, useRef} from 'react'; import {useStandaloneSearchBox} from '~/lib/commerce-engine.ts'; import {useNavigate} from '@remix-run/react'; export function StandaloneSearchBox() { const searchBox = useStandaloneSearchBox(); const inputRef = useRef(null); const navigate = useNavigate(); useEffect(() => { inputRef.current?.focus(); }, []); useEffect(() => { if (searchBox.state.redirectTo === '/search') { navigate( `${searchBox.state.redirectTo}?q=${encodeURIComponent( searchBox.state.value, )}`, ); } }, [searchBox.state.redirectTo, searchBox.state.value]); const handleSuggestionClick = (suggestion: string) => { searchBox.methods?.updateText(suggestion); inputRef.current!.value = suggestion; searchBox.methods?.showSuggestions(); }; return (
searchBox.methods?.updateText(e.target.value)} onFocus={() => searchBox.methods?.showSuggestions()} /> {searchBox.state.suggestions.length > 0 && (
{searchBox.state.suggestions.map((suggestion) => (
)}
); } ``` For a more detailed explanation of how the search box component works, see [Build search interfaces](https://docs.coveo.com/en/p27e1343/). ## Display the component Now that you have all the pieces in place, you can display the search box component in your Hydrogen storefront. ```tsx // app/routes/search.tsx import { StandaloneSearchBox } from "~/components/StandaloneSearchBox" import { StandaloneProvider } from "~/lib/providers" import { ServerSideNavigatorContextProvider, ClientSideNavigatorContextProvider } from "~/lib/navigator-provider" import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { fetchStaticState, standaloneEngineDefinition, StandaloneStaticState } from '~/lib/commerce-engine'; import {useLoaderData} from '@remix-run/react'; export async function loader({request, context}: LoaderFunctionArgs) { standaloneEngineDefinition.setNavigatorContextProvider( () => new ServerSideNavigatorContextProvider(request), ); const staticState = await fetchStaticState({ k: 'standaloneEngineDefinition', url: `https://shop.barca.group`, }); return staticState } export default function SearchPage() { const staticState = useLoaderData(); return ( navigatorContext={new ClientSideNavigatorContextProvider()} <2> staticState={staticState as StandaloneStaticState} <3> > ) } ``` <1> To use the Headless provider, wrap your components with the provider and provide two required props. <2> 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. <3> Use the already defined [helper method](#fetch-static-headless-state) to fetch the static Headless state. ## Next steps Now that you've seen an example of how to set up the Headless library in a Hydrogen storefront, you can start building your own components with the Headless library. For more details on building a search page, see [Build search interfaces](https://docs.coveo.com/en/p27e1343/). To learn more about the core concepts in this article, see [Core concepts](https://docs.coveo.com/en/p3ae0283/).