Skip to content

Embed Recommendations in Your Site

⏱ 25 minutes intermediate
📜Corecommercecms

A recommendation model that sits in a dashboard does nothing. Its value is realized only when recommendations appear on your pages, in your emails, and across your customer touchpoints. The way you embed recommendations affects performance, user experience, and how well the engine learns from visitor interactions.

This guide covers three embedding approaches: CMS blocks for no-code placement, API integration for full control, and client-side widgets for quick deployment. Each approach includes fallback handling so new visitors and edge cases are covered.

  1. Add a recommendation block to CMS pages
  2. Integrate with the Recommendations API for custom rendering
  3. Implement client-side embedding with JavaScript
  4. Configure fallback strategies for cold-start scenarios

The simplest approach is the CMS recommendation block. Content authors drag it onto any content area and configure which model it uses — no code changes required.

For content authors:

  1. Edit a page in the CMS editor
  2. In a content area, click Add Block
  3. Select Recommendation Block
  4. Configure the block:
    • Model — Select the recommendation model to use (e.g., “Product Page - Similar Items”)
    • Number of items — How many recommendations to display (typically 3-6)
    • Display template — Choose a layout (grid, list, carousel)
  5. Publish the page

The block handles API calls, rendering, and tracking automatically. When a visitor views the page, the block fetches recommendations for the current context (the page or product being viewed) and renders them using the selected template.

For full control over how recommendations appear, call the Recommendations API directly and render the results yourself.

Fetch recommendations server-side
csharp
using System.Net.Http;
using System.Text.Json;

public class RecommendationService
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly string _baseUrl;

    public RecommendationService(
        HttpClient httpClient,
        string apiKey,
        string baseUrl)
    {
        _httpClient = httpClient;
        _apiKey = apiKey;
        _baseUrl = baseUrl;
    }

    public async Task<List<RecommendedItem>>
        GetRecommendations(
            string modelId,
            string contextItemId,
            string visitorId,
            int count = 4)
    {
        var request = new HttpRequestMessage(
            HttpMethod.Get,
            $"{_baseUrl}/api/v1/recommendations"
            + $"?modelId={modelId}"
            + $"&contextItem={contextItemId}"
            + $"&visitorId={visitorId}"
            + $"&count={count}");

        request.Headers.Add(
            "Authorization", $"Bearer {_apiKey}");

        var response = await _httpClient
            .SendAsync(request);
        response.EnsureSuccessStatusCode();

        var json = await response.Content
            .ReadAsStringAsync();

        return JsonSerializer
            .Deserialize<List<RecommendedItem>>(json);
    }
}

public class RecommendedItem
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string ImageUrl { get; set; }
    public string Url { get; set; }
    public double Score { get; set; }
}

API parameters:

ParameterRequiredDescription
modelIdYesThe ID of the recommendation model to query
contextItemDependsThe current item ID (required for “similar items” strategies, not for “personalized for you”)
visitorIdYesThe ODP visitor ID (from tracking cookie)
countNoNumber of recommendations to return (default: 4, max: 20)

For client-side embedding, use the Recommendations JavaScript SDK. This approach works well for single-page applications and sites where you want recommendations to load after the initial page render.

Client-side recommendation widget
javascript
// Initialize the Recommendations SDK
const recsClient = window.optimizely.recommendations.create({
  apiKey: 'YOUR_PUBLIC_API_KEY',
  trackerId: 'YOUR_ODP_TRACKER_ID'
});

// Fetch recommendations for the current page
async function loadRecommendations(containerId, modelId) {
  const container = document.getElementById(containerId);
  
  try {
    const response = await recsClient.getRecommendations({
      modelId: modelId,
      contextItem: getCurrentPageId(),
      count: 4
    });

    if (response.items.length === 0) {
      // No recommendations available -- show fallback
      renderFallback(container);
      return;
    }

    renderRecommendations(container, response.items);

    // Track impression for model learning
    recsClient.trackImpression({
      modelId: modelId,
      items: response.items.map(item => item.id)
    });
  } catch (error) {
    console.error('Recommendations failed:', error);
    renderFallback(container);
  }
}

function renderRecommendations(container, items) {
  const html = items.map(item => `
    <a href="${item.url}" class="rec-item"
       data-rec-id="${item.id}">
      <img src="${item.imageUrl}"
           alt="${item.title}" />
      <span>${item.title}</span>
    </a>
  `).join('');

  container.innerHTML = `
    <div class="rec-widget">
      <h3>Recommended for you</h3>
      <div class="rec-grid">${html}</div>
    </div>
  `;

  // Track clicks for model learning
  container.querySelectorAll('.rec-item')
    .forEach(el => {
      el.addEventListener('click', () => {
        recsClient.trackClick({
          modelId: modelId,
          itemId: el.dataset.recId
        });
      });
    });
}

Recommendations will fail or return empty results in predictable scenarios. Plan for each one.

ScenarioWhy it happensFallback approach
New visitor, no historyNo behavioral data to personalize fromShow trending items or editorially curated picks
New item, no interactionsItem has not been viewed or purchased enoughUse content-based matching on item attributes
API timeout or errorNetwork issues, service degradationShow cached recommendations or static content
All items excluded by rulesOverly aggressive business rulesRelax rules or show a different model’s output
Fallback implementation
javascript
async function getRecommendationsWithFallback(
  primaryModelId,
  fallbackModelId,
  contextItemId
) {
  // Try the primary model first
  try {
    const primary = await recsClient
      .getRecommendations({
        modelId: primaryModelId,
        contextItem: contextItemId,
        count: 4
      });

    if (primary.items.length >= 2) {
      return primary.items;
    }
  } catch (e) {
    // Primary model failed, continue to fallback
  }

  // Fallback to trending model
  try {
    const fallback = await recsClient
      .getRecommendations({
        modelId: fallbackModelId,
        count: 4
      });

    return fallback.items;
  } catch (e) {
    // Both models failed
    return getCachedRecommendations();
  }
}

function getCachedRecommendations() {
  // Return a static set of curated items
  // as the last resort fallback
  const cached = localStorage
    .getItem('last-good-recs');
  return cached
    ? JSON.parse(cached)
    : [];
}

For the engine to learn and improve, it needs to know when visitors see and click recommendations. Tracking is essential — without it, models cannot retrain effectively.

Required tracking events:

EventWhen to fireWhat it tells the engine
ImpressionWhen recommendations render in the viewportWhich items were shown to the visitor
ClickWhen a visitor clicks a recommended itemWhich recommendations the visitor found relevant
ConversionWhen a click leads to a purchase or goal completionWhich recommendations drove business outcomes

The CMS recommendation block handles tracking automatically. If you use the API or client-side SDK, you must implement tracking yourself (as shown in the code examples above).

  • Load recommendations asynchronously. Do not block page rendering while waiting for the API. Render the page shell first, then populate the recommendation widget.
  • Cache API responses. For server-side integration, cache recommendation results for 5-15 minutes per visitor-context combination. This reduces API calls without significantly impacting personalization quality.
  • Lazy-load below-the-fold widgets. If recommendations appear further down the page, use intersection observer to fetch them only when the visitor scrolls near them.
  • Set a timeout. Configure a 2-3 second timeout on API calls. If the API does not respond in time, show the fallback immediately rather than leaving an empty space.
IssueCauseFix
Widget shows “No recommendations”Model not published, or insufficient dataPublish the model and verify ODP tracking is sending events
Same recommendations on every pageContext item not passed to the APIInclude the current page or product ID as contextItem
Recommendations not improving over timeImpression and click tracking not implementedAdd tracking events for impressions and clicks
Slow page loadRecommendations blocking page renderSwitch to async loading with a loading skeleton