Building an Intelligent Related Content Widget with Sitecore Search and Tag-Based Sorting

Building an Intelligent Related Content Widget with Sitecore Search and Tag-Based Sorting

By admin August 5, 2025

When building content-heavy websites with Sitecore XM Cloud, one of the biggest challenges is helping users discover related content that truly matches their interests. Traditional search approaches often fall short because they rely on basic keyword matching or simple taxonomy filters. We needed a more intelligent solution that could understand content relationships and prioritize the most relevant items.

That's how we developed our Related Content Widget using Sitecore Search's powerful custom combined_tags field and custom client-side sorting logic. This approach allows us to create highly personalized content recommendations that adapt to each page's context.

The Challenge

Our content editors were struggling with several issues:

  • Related content wasn't contextually relevant to the current page
  • Search results were ordered by basic relevance, not by actual content relationships
  • No way to prioritize specific content items when needed
  • Limited flexibility for different content types and use cases

We needed a solution that could understand content relationships through multiple taxonomy fields and provide intelligent sorting based on the number of matching tags.

Sitecore Search and the Combined Tags Field

Sitecore Search provides a powerful search API that can be extended with custom fields. Our solution leverages a custom combined_tags attribute that we populated using Sitecore Search's document extractor. This field combines all taxonomy values from different facets (applications, markets, solutions, technologies, content types, etc.) into a single searchable array.

Here's how the combined_tags field is populated:


// Document extractor configuration for combined_tags
const combinedTags = [
  ...item.applications,
  ...item.markets,
  ...item.solutions,
  ...item.technologies,
  ...item.content_type,
  ...item.resource_type
].filter(Boolean);

The Solution: Smart Sorting with Combined Tags

Our implementation works in three key steps:

  1. Tag Collection: Extract all relevant tags from the current page's facets
  2. Search Filtering: Use FilterAnyOf on the combined_tags field to find matching content
  3. Client-Side Sorting: Sort results by the number of matching tags, with secondary sorting by date and alphabetical order

Priority Pages Feature

One of the key features of our solution is the ability to pre-select priority pages in the component's datasource. These priority pages will always appear at the top of the results, regardless of their tag match count. This gives content editors complete control over which content should be highlighted for specific pages.

Here's how priority pages work:


// Priority pages are specified in the component datasource
const priorityPages = "item-id-1|item-id-2|item-id-3";

// These items will always appear first, in the order specified
const priorityItemIds = priorityPages.split('|').map(id => id.trim());

Implementation Details

Here's the core sorting function that powers our intelligent content recommendations:


// Helper function to sort articles by tag match count
const sortArticlesByTagMatches = (
  articles: RelatedArticleModel[],
  searchTags: string[],
  priorityItemIds: string[] = []
): RelatedArticleModel[] => {
  return [...articles].sort((a, b) => {
    const aKey = a.sc_id || a.id;
    const bKey = b.sc_id || b.id;

    // Priority items always come first
    if (priorityItemIds.length > 0) {
      const aIsPriority = priorityItemIds.includes(aKey);
      const bIsPriority = priorityItemIds.includes(bKey);
      
      if (aIsPriority && !bIsPriority) return -1;
      if (!aIsPriority && bIsPriority) return 1;
      
      if (aIsPriority && bIsPriority) {
        const aIndex = priorityItemIds.indexOf(aKey);
        const bIndex = priorityItemIds.indexOf(bKey);
        return aIndex - bIndex;
      }
    }

    // Count matching tags for each article
    const aMatchingTags = a.combined_tags?.filter(tag => 
      searchTags.includes(tag)
    ).length || 0;
    
    const bMatchingTags = b.combined_tags?.filter(tag => 
      searchTags.includes(tag)
    ).length || 0;

    // Sort by tag match count (descending)
    if (bMatchingTags !== aMatchingTags) {
      return bMatchingTags - aMatchingTags;
    }

    // Secondary sorting by published date (newest first)
    if (a.published_date && b.published_date) {
      const dateDiff = b.published_date - a.published_date;
      if (dateDiff !== 0) return dateDiff;
    }

    // Tertiary sorting by name (alphabetical)
    const aName = (a.name || a.title || '').toLowerCase();
    const bName = (b.name || b.title || '').toLowerCase();
    return aName.localeCompare(bName);
  });
};

Search Request Configuration

Here's how we configure the search request to use combined tags:


// Extract all tags from page facets
const allTags: string[] = [];
pageFacets.forEach((facet) => {
  if (facet.value && facet.value.length > 0) {
    const facetValues = facet.value.map((item) => item.text);
    allTags.push(...facetValues);
  }
});

// Create combined_tags filter
if (allTags.length > 0) {
  const combinedTagsFilter = new FilterAnyOf('combined_tags', allTags);
  facetFilters.push(combinedTagsFilter);
}

Key Benefits

This approach provides several advantages:

  • Contextual Relevance: Content is matched based on multiple taxonomy fields, not just keywords
  • Intelligent Sorting: Results are ordered by the number of matching tags, ensuring the most relevant content appears first
  • Priority Page Control: Content editors can pre-select specific pages that should always appear at the top
  • Flexible Prioritization: Priority pages are shown first, followed by content sorted by tag matches
  • Fallback Sorting: When tag matches are equal, content is sorted by date and then alphabetically
  • Performance: Client-side sorting is fast and doesn't require additional API calls

Usage Example

Here's how to use the Related Content Widget in your Sitecore XM Cloud pages:


<RelatedWidgetComponent
  sources={['your-source-id']}
  templateID={['your-template-id']}
  pageFacets={pageFacets}
  limit={6}
  excludeItemId={currentItemId}
/>

Conclusion

This intelligent related content widget can be configured to automatically retrieve the current page template and its tags, then render related items without any changes in the datasource. The component intelligently analyzes the page's taxonomy facets and finds the most relevant content based on tag matching.

The same code can be used with different variants to achieve various visual presentations, such as 3-card grids, 2-card layouts, or carousel views. This flexibility allows content editors to choose the most appropriate display format for their content while maintaining the same intelligent sorting and filtering logic.

This solution has significantly improved content discovery on our Sitecore XM Cloud sites, providing users with highly relevant recommendations that adapt to each page's context while giving content editors complete control over priority content and display options.

Featured Blogs