diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts
index 2462bc462b..13911666d7 100644
--- a/packages/headless/src/app/commerce-ssr-engine/common.ts
+++ b/packages/headless/src/app/commerce-ssr-engine/common.ts
@@ -97,10 +97,19 @@ export function buildControllerDefinitions<
? definition['standalone'] === false
: false;
+ const unavailabeInRecs =
+ // TODO: use this disjunction pattern for all other conditions
+ (solutionType === SolutionType['recommendation'] &&
+ !('recommendation' in definition)) ||
+ ('recommendation' in definition &&
+ definition['recommendation'] === false &&
+ solutionType === SolutionType['recommendation']);
+
if (
unavailableInSearchSolutionType ||
unavailableInListingSolutionType ||
- unavailableInStandaloneSolutionType
+ unavailableInStandaloneSolutionType ||
+ unavailabeInRecs
) {
return null;
}
diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx
index 740a6869fa..fe2cd83548 100644
--- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx
+++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx
@@ -6,11 +6,15 @@ import FacetGenerator from '@/components/facets/facet-generator';
import Pagination from '@/components/pagination';
import ProductList from '@/components/product-list';
import ListingProvider from '@/components/providers/listing-provider';
+import RecommendationProvider from '@/components/providers/recommendation-provider';
import Recommendations from '@/components/recommendation-list';
import Sort from '@/components/sort';
import StandaloneSearchBox from '@/components/standalone-search-box';
import Summary from '@/components/summary';
-import {listingEngineDefinition} from '@/lib/commerce-engine';
+import {
+ listingEngineDefinition,
+ recommendationEngineDefinition,
+} from '@/lib/commerce-engine';
import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider';
import {defaultContext} from '@/utils/context';
import {headers} from 'next/headers';
@@ -54,6 +58,11 @@ export default async function Listing({params}: {params: {category: string}}) {
},
});
+ const recStaticState = await recommendationEngineDefinition.fetchStaticState([
+ 'popularBoughtRecs',
+ 'popularViewedRecs',
+ ]);
+
return (
-
+
+
+
diff --git a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx
index d0112efb94..7264137062 100644
--- a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx
+++ b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx
@@ -28,7 +28,7 @@ export default function ListingProvider({
useEffect(() => {
listingEngineDefinition
.hydrateStaticState({
- searchAction: staticState.searchAction,
+ searchActions: staticState.searchActions,
controllers: {
cart: {
initialState: {items: staticState.controllers.cart.state.items},
diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx
new file mode 100644
index 0000000000..deea79c460
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx
@@ -0,0 +1,63 @@
+'use client';
+
+import {
+ recommendationEngineDefinition,
+ RecommendationHydratedState,
+ RecommendationStaticState,
+} from '@/lib/commerce-engine';
+import {NavigatorContext} from '@coveo/headless-react/ssr-commerce';
+import {PropsWithChildren, useEffect, useState} from 'react';
+
+interface RecommendationPageProps {
+ staticState: RecommendationStaticState;
+ navigatorContext: NavigatorContext;
+}
+
+export default function RecommendationProvider({
+ staticState,
+ navigatorContext,
+ children,
+}: PropsWithChildren) {
+ const [hydratedState, setHydratedState] = useState<
+ RecommendationHydratedState | undefined
+ >(undefined);
+
+ // Setting the navigator context provider also in client-side before hydrating the application
+ recommendationEngineDefinition.setNavigatorContextProvider(
+ () => navigatorContext
+ );
+
+ useEffect(() => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (recommendationEngineDefinition.hydrateStaticState as any)({
+ searchActions: staticState.searchActions,
+ // controllers: { },
+ }).then(({engine, controllers}) => {
+ setHydratedState({engine, controllers});
+ // Refreshing recommendations in the browser after hydrating the state in the client-side
+ // Recommendation refresh in the server is not supported yet.
+ // controllers.popularBoughtRecs.refresh(); // FIXME: does not work
+ });
+ }, [staticState]);
+
+ if (hydratedState) {
+ return (
+
+ <>{children}>
+
+ );
+ } else {
+ return (
+
+ {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'.
+ Type 'bigint' is not assignable to type 'ReactNode'.*/}
+ <>{children}>
+
+ );
+ }
+}
diff --git a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx
index e9e099e286..7467928bb6 100644
--- a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx
+++ b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx
@@ -28,7 +28,7 @@ export default function SearchProvider({
useEffect(() => {
searchEngineDefinition
.hydrateStaticState({
- searchAction: staticState.searchAction,
+ searchActions: staticState.searchActions,
controllers: {
cart: {
initialState: {items: staticState.controllers.cart.state.items},