Build search interfaces (SSR)
Build search interfaces (SSR)
|
The Headless Commerce SSR utilities are in open beta. Contact your Coveo representative for support in adopting this. |
Building a server-side rendered (SSR) search interface with Coveo Headless involves three main components: a search box, a search page, and a search provider.
The Coveo Headless React package provides two types of search box controllers: the SearchBox
and the StandaloneSearchBox
.
It also exposes functions to create a search provider to manage the search page and results.
-
SearchBox
: Use this controller to create a search box component in your search interface, submit queries, and display query suggestions. This controller is used on the search page itself. -
StandaloneSearchBox
: Use this controller to create a standalone search box that redirects to your search page. This controller is used on every page of your app except the search page. -
Search provider: Use this provider to manage search results, such as facets, sorting, and pagination, ensuring a seamless user experience.
Complete examples are available in the Headless repository.
Prerequisites
Before you begin building your search interface, make sure you:
Creating a search box
To create a search box, use the SearchBox
and StandaloneSearchBox
controllers.
Using the SearchBox
controller
When defining your commerce engine, specify the SearchBox
controller and retrieve the corresponding hook, as follows:
// lib/commerce-engine-config.ts
import {
defineSearchBox,
// ...
} from '@coveo/headless-react/ssr-commerce';
export default {
// ...
controllers: {
searchBox: defineSearchBox(),
// ...
},
} satisfies CommerceEngineDefinitionOptions;
Next, create a search box component that uses the SearchBox
controller hook.
// components/Search-box.tsx
'use client';
import {
useSearchBox,
} from '@/lib/commerce-engine';
export default function SearchBox() {
const searchBox = useSearchBox();
const onSearchBoxInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
searchBox.methods?.updateText(e.target.value);
};
return (
<div>
<input
type="search"
aria-label="searchbox"
placeholder="search"
value={searchBox.state.value}
onChange={(e) => onSearchBoxInputChange(e)}
></input>
{searchBox.state.value !== '' && (
<span>
<button onClick={searchBox.methods?.clear}>X</button>
</span>
)}
<button onClick={searchBox.methods?.submit}>Search</button>
{searchBox.state.suggestions.length > 0 && (
<ul>
Suggestions :
{searchBox.state.suggestions.map((suggestion, index) => (
<li key={index}>
<button
onClick={() =>
searchBox.methods?.selectSuggestion(suggestion.rawValue)
}
dangerouslySetInnerHTML={{
__html: suggestion.highlightedValue,
}}
></button>
</li>
))}
</ul>
)}
</div>
);
}
Retrieve the SearchBox controller hook. |
|
Update the query when the user types in the search box by calling the updateText method on the SearchBox controller hook. |
|
Display query suggestions when the user types in the search box, if suggestions are available.
When the user clicks a suggestion, call the selectSuggestion method on the SearchBox controller hook to submit the selected suggestion as a query. |
This component can now be included in your search page.
Using the StandaloneSearchBox
controller
In addition to creating a search box component, create a lightweight standalone search box component that utilizes the StandaloneSearchBox
controller to redirect to your search page.
This standalone search box component should be included on every page of your app except the search page.
When defining your commerce engine, specify the StandaloneSearchBox
controller and retrieve the corresponding hook.
// lib/commerce-engine-config.ts
import {
defineStandaloneSearchBox,
// ...
} from '@coveo/headless-react/ssr-commerce';
export default {
// ...
controllers: {
standaloneSearchBox: defineStandaloneSearchBox({
options: {redirectionUrl: '/search'},
}),
// ...
},
} satisfies CommerceEngineDefinitionOptions;
Define the StandaloneSearchBox controller hook by passing the redirection URL to the search page.
When the user submits a query in this search box, the |
Next, create a component that uses this StandaloneSearchBox
controller.
It looks similar to the search box component, with the addition of redirection logic.
'use client';
import {
useStandaloneSearchBox,
} from '@/lib/commerce-engine';
import {useRouter} from 'next/navigation';
import {useEffect} from 'react';
export default function StandaloneSearchBox() {
const standaloneSearchBox = useStandaloneSearchBox();
const router = useRouter();
useEffect(() => {
if (standaloneSearchBox.state.redirectTo === '/search') {
const url = `${standaloneSearchBox.state.redirectTo}#q=${encodeURIComponent(standaloneSearchBox.state.value)}`;
router.push(url, {scroll: false});
standaloneSearchBox.methods?.afterRedirection();
}
}, [standaloneSearchBox.state.redirectTo, standaloneSearchBox.state.value, router, standaloneSearchBox.methods]);
const onSearchBoxInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
standaloneSearchBox.methods?.updateText(e.target.value);
};
return (
<div>
<input
type="search"
aria-label="searchbox"
placeholder="search"
value={standaloneSearchBox.state.value}
onChange={(e) => onSearchBoxInputChange(e)}
></input>
{standaloneSearchBox.state.value !== '' && (
<span>
<button onClick={standaloneSearchBox.methods?.clear}>X</button>
</span>
)}
<button onClick={() => standaloneSearchBox.methods?.submit()}>Search</button>
{standaloneSearchBox.state.suggestions.length > 0 && (
<ul>
Suggestions :
{standaloneSearchBox.state.suggestions.map((suggestion, index) => (
<li key={index}>
<button
onClick={() =>
standaloneSearchBox.methods?.selectSuggestion(suggestion.rawValue)
}
dangerouslySetInnerHTML={{
__html: suggestion.highlightedValue,
}}
></button>
</li>
))}
</ul>
)}
</div>
);
}
The Next.js useRouter hook is used to access the router object and navigate to the search page. |
|
Redirect to the search page when the user submits a query and the redirectTo property of the controller is set to /search . |
|
This method resets the state after the redirection. |
A complete example, including a sample implementation of the standalone search box component, is available in the Headless repository.
Displaying the search page with the search provider
In addition to rendering the search box, you need to display the search results on the search page, using your search provider.
// app/search/page.tsx
import ContextDropdown from '@/components/context-dropdown';
import FacetGenerator from '@/components/facets/facet-generator';
import ParameterManager from '@/components/parameter-manager';
import ProductList from '@/components/product-list';
import {SearchProvider} from '@/components/providers/providers';
import SearchBox from '@/components/search-box';
import {searchEngineDefinition} from '@/lib/commerce-engine';
import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider';
import {defaultContext} from '@/utils/context';
import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce';
import {headers} from 'next/headers';
import Pagination from '@/components/pagination';
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);
const items = // ...
const staticState = await searchEngineDefinition.fetchStaticState({
controllers: {
cart: {initialState: {items}},
context: {
language: defaultContext.language,
country: defaultContext.country,
currency: defaultContext.currency,
view: {
url: 'https://sports.barca.group/search',
},
},
parameterManager: {initialState: {parameters}},
},
});
return (
<SearchProvider
staticState={staticState}
navigatorContext={navigatorContext.marshal}
>
<ParameterManager url={navigatorContext.location} />
<ContextDropdown useCase="search" />
<div style={{display: 'flex', flexDirection: 'row'}}>
<div style={{flex: 1}}>
<FacetGenerator />
</div>
<div style={{flex: 2}}>
<SearchBox />
<ProductList />
<Pagination></Pagination>
</div>
</div>
</SearchProvider>
);
}
export const dynamic = 'force-dynamic';
Retrieve the search parameters from the URL. | |
Create a navigation context provider. | |
Deserialize the search parameters. | |
Retrieve cart items. | |
Wrap your components with your search provider. | |
Use your parameter manager component. | |
Let your users change context. | |
Display the facets generated by the FacetGenerator controller. |
|
Display the sorting options generated by the Sort controller. |
|
Render the list of products returned by the search. |
|
Display the pagination generated by the Pagination controller. |
A complete example of how to build a search page is available in the Headless repository.
Within the sample project, you can find additional components that were omitted in this article, such as the BreadcrumbManager
(displays a summary of the currently active facet values) and Summary
(provides a summary of search results such as the number of results returned).