Creating a Related Content List Using Sitecore Search Widgets in Your XM Cloud and Next.js Project

Learn how to integrate your Sitecore Search widgets into your headless Next.js project

April 23, 2024

By John Flores

We learned about how you can setup Sitecore Search and preparing your widgets in this blog but we want to know how we can use your widgets to create Sitecore renderings and integrate them in it. Another requirement we would like to have is automatically get a list of related content based on the page you are on, this usually happens when you’re on a blog or article page and you would want to automatically get some related pages on the bottom of your page.

We have an example below where we have a blog article and we want three related blogs to be featured at the bottom. Using Sitecore Search and the widgets available we can easily implement this.

Screenshot outlining a related content component on a mock blog page on the Fishtank website

Setting Up Your Component

I won’t put too much details but we will need to get all the required things in order to get a Sitecore Rendering. We need the template in place as well as the JSON Rendering. Let’s call this component as RelatedContent. We then create our RelatedContent.tsx file inside the components folder. The code below is a good example of how you would want to setup your component. We can go step by step and I’ll explain each part and it’s importance.

import { Field } from '@sitecore-jss/sitecore-jss-nextjs';
import React from 'react';
import { ComponentProps } from 'lib/component-props';
import { Environment, WidgetsProvider } from '@sitecore-search/react';
import RelatedPages from 'widgets/RelatedPages/index';
import config from 'temp/config';

type Fields = {
  heading?: Field<string>;
};

type RelatedContentProps= ComponentProps & {
  fields: Fields;
};

export const Default = ({ fields }: RelatedContentProps) => {
  const SEARCH_ENV = config?.searchEnv as Environment;
  const SEARCH_CUSTOMER_KEY = config?.searchCustomerKey;
  const SEARCH_API_KEY = config?.searchApiKey;

  return (
    <div>
      <WidgetsProvider
        env={SEARCH_ENV}
        customerKey={SEARCH_CUSTOMER_KEY}
        apiKey={SEARCH_API_KEY}
        publicSuffix={true}
      >
        <RelatedPages rfkId="rfkid_x" defaultKeyphrase={fields?.heading?.value ?? ''}></RelatedPages>
      </WidgetsProvider>
    </div>
  );
};

Make sure you keep your keys safe and private, I’ve configured it to be added on the temporary config file. I’ll leave it to you if you have a preference on how you handle your API Keys, these are the private data that will be needed to connect to Sitecore Search.

  const SEARCH_ENV = config?.searchEnv as Environment;
  const SEARCH_CUSTOMER_KEY = config?.searchCustomerKey;
  const SEARCH_API_KEY = config?.searchApiKey;

You will also need to wrap your widget with the Sitecore Search’s WidgetsProvider. Don’t forget to grab the rfkId from the widget you created in https://cec.sitecorecloud.io/. In our example we created a RelatedPages widget which we are basing off a key phrase with the value being the heading of the page. Below you will see the RelatedPagesComponent widget code.

import { WidgetDataType, useSearchResults, widget } from '@sitecore-search/react';
import { ArticleCardStyled } from './styled';
import PropTypes from 'prop-types';

export const RelatedPagesComponent = ({ defaultKeyphrase = '', defaultItemsPerPage = 3 }) => {
  const language = 'en';
  const {
    widgetRef,
    queryResult: { data: { total_item: totalItems = 0, content: articles = [] } = {} },
  } = useSearchResults({
    query: (query) => {
      query.getRequest();
    },
    state: {
      itemsPerPage: defaultItemsPerPage,
      keyphrase: defaultKeyphrase,
    },
  });

  return (
      {totalItems > 0 && (
        <div ref={widgetRef}>
          {articles.map((a) => (
            <ArticleCardStyled.Root
              className="card flex flex-col overflow-hidden rounded-1"
              key={`${a.id}@${a.source_id}@${language}`}
            >
              <ArticleCardStyled.Title className="mb-4">{a.title}</ArticleCardStyled.Title>
            </ArticleCardStyled.Root>
          ))}
        </div>
      )}
  );
};

RelatedPagesComponent.propTypes = {
  defaultItemsPerPage: PropTypes.number,
  defaultKeyphrase: PropTypes.string,
};

const RelatedResults = widget(RelatedPagesComponent, WidgetDataType.SEARCH_RESULTS, 'content');

export default RelatedResults;

The strategy we are looking at here is using useSearchResults instead of useRecommendations since we want results based on a specific criteria of the page and not the user.

  const {
    widgetRef,
    queryResult: { data: { total_item: totalItems = 0, content: articles = [] } = {} },
  } = useSearchResults({
    query: (query) => {
      query.getRequest();
    },
    state: {
      itemsPerPage: defaultItemsPerPage,
      keyphrase: defaultKeyphrase,
    },
  });

We then need to setup this component as being a widget.

const RelatedResults = widget(
   RelatedPagesComponent,
   WidgetDataType.SEARCH_RESULTS,
   'content'
);

We use the widget function with needs at least 3 arguments. First would be the component itself, next is based on what hook we are using here are a couple of examples of the data type.

  • WidgetDataType.SEARCH_RESULTS
  • WidgetDataType.PREVIEW_SEARCH
  • WidgetDataType.RECOMMENDATION

Then most of the time you’ll just need to pass the string content .

Leveraging Sitecore Search for Enhanced XM Cloud Development

We have gone over a good example of what other ways we can use the Sitecore Search Widget and how easily you can integrate it into one of your Sitecore Renderings. There are more ways you can integrate Sitecore Search into your headless XM Cloud project and push your development further. We’ll dive more into different ways and details on other widgets like Preview Search and maybe even Recommendations in the future.



John Headshot

John Flores

Front-End Developer

John is a Senior Front-End Developer who is passionate about design and development. Outside of work, John has wide range of hobbies, from his plant collection, being a dog daddy, and a foodie.